# 【Treeman】總覽

# 舊wiki連結

[Treeman的 TiddlyWiki — 紀錄著一個工程師的點點滴滴 (treemanou.github.io)](https://treemanou.github.io/)

# 關於攻城獅的小本本

[![image-1633768163145.png](http://bookstack.treemanou.com/uploads/images/gallery/2021-10/scaled-1680-/qHU5zFzSGE9e42oO-image-1633768163145.png)](http://bookstack.treemanou.com/uploads/images/gallery/2021-10/qHU5zFzSGE9e42oO-image-1633768163145.png)

## "學海無涯，腦袋記不住的，就讓電腦幫你記吧"

**~只是一個普通工程師的小抄**

**舊站連結:[Treeman的 TiddlyWiki — 紀錄著一個工程師的點點滴滴 (treemanou.github.io)](https://treemanou.github.io/)**

# 開源資源

# 前端軍火庫

- 免費圖庫 
    - [https://unsplash.com/](https://unsplash.com/)
    - [https://storyset.com/ ](https://storyset.com/)
- Sortable 拖曳元件庫 
    - [SortableJS](http://sortablejs.github.io/Sortable/#thresholds) 官方網站
    - [sortable.js中文文档 - itxst.com](https://www.itxst.com/sortablejs/neuinffi.html)
    - [Vue.Draggable: Vue 插件](https://github.com/SortableJS/Vue.Draggable)
- Loading 
    - [https://skeleton-loader.web.app/](https://skeleton-loader.web.app/) (**Skeleton Loading Screen**)
- Echart 
    - [官網首頁](https://echarts.apache.org/zh/index.html)
    - [甘特圖](https://echarts.apache.org/examples/zh/editor.html?c=custom-gantt-flight)
- vis-timeline
- - [甘特圖](https://visjs.github.io/vis-timeline/)
- 測試用api 
    - [Random User Generator | Home](https://randomuser.me/)
    - h[ttps://jsonplaceholder.typicode.com/guide/](https://jsonplaceholder.typicode.com/guide/)
    - [免費圖庫api](https://unsplash.com/developers)
- 色票 
    - [coolors](https://coolors.co/)  
        隨機產生色票 [https://coolors.co/generate](https://coolors.co/generate)
- React UI 
    - [https://mui.com/](https://mui.com/)
    - [https://www.chakra-ui.com/](https://www.chakra-ui.com/)
- shadcn UI 
    - [https://ui.shadcn.com/docs/installation](https://ui.shadcn.com/docs/installation)

# 後端資源

[2020年排名前20的基于SpringBoot搭建的开源项目，帮你快速进行项目搭建！ - 掘金 (juejin.cn)](https://juejin.cn/post/6844904205547749383)

# 色票

[![image-1718275373549.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-06/scaled-1680-/hBD64aJkWLg4PPYg-image-1718275373549.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-06/hBD64aJkWLg4PPYg-image-1718275373549.png)

[![image-1718275387288.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-06/scaled-1680-/N4Q5k5nE1az2Muz0-image-1718275387288.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-06/N4Q5k5nE1az2Muz0-image-1718275387288.png)

[![image-1718275401238.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-06/scaled-1680-/dBHAcSAlMKABUUhg-image-1718275401238.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-06/dBHAcSAlMKABUUhg-image-1718275401238.png)

[![image-1718275413591.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-06/scaled-1680-/iDaQp9J8yqPwZUwn-image-1718275413591.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-06/iDaQp9J8yqPwZUwn-image-1718275413591.png)

[![image-1718275423848.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-06/scaled-1680-/wzefdO3EiS2D88GM-image-1718275423848.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-06/wzefdO3EiS2D88GM-image-1718275423848.png)

[![image-1718275436152.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-06/scaled-1680-/SqdZjPBC8iO5UEG5-image-1718275436152.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-06/SqdZjPBC8iO5UEG5-image-1718275436152.png)

[![image-1718275453578.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-06/scaled-1680-/ba8DAbWc4Oo6lnTY-image-1718275453578.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-06/ba8DAbWc4Oo6lnTY-image-1718275453578.png)

# 資安相關

# 【Linux】禁止密碼登入(使用金鑰)

修改 /etc/ssh/sshd\_config

```shell
vim /etc/ssh/sshd_config

## 修改 PubkeyAuthentication

PubkeyAuthentication yes
#使用 ssh key 登入
PasswordAuthentication no
#禁止密碼登入
```

重啟 ssh 服務

```shell
sudo systemctl restart sshd.service
```

# 【shell】deny_hack_ip.sh

簡單阻擋 try 帳號 ip

```shell
#! /bin/bash
#cat /var/log/secure|awk '/Failed/{print $(NF-3)}'|sort|uniq -c|awk '{print $2"="$1;}' > /root/block.txt
cat /var/log/secure|awk '/Invalid user/{print $(NF-2)}'|sort|uniq -c|awk '{print $2"="$1;}' > /root/block.txt
DEFINE="10"
for i in `cat  /root/block.txt`
do
    IP=`echo | awk '{split("'${i}'", array, "=");print array[1]}'`
    NUM=`echo | awk '{split("'${i}'", array, "=");print array[2]}'`
    if [ $NUM -gt $DEFINE ];then
     grep $IP /etc/hosts.deny > /dev/null
      if [ $? -gt 0 ];then
          echo "sshd:$IP:deny" >> /etc/hosts.deny
      fi
    fi
done
```

# 資安相關連結

下面整理一份「資安一定要知道」的網站速查表，平常做**漏洞管理、暴露面盤點、威脅模型、設定基線、事件初判**會最常用到的那幾類。

<table id="bkmrk-%E9%A1%9E%E5%88%A5-%E7%B6%B2%E7%AB%99%2F%E7%B5%84%E7%B9%94-%E4%BD%A0%E4%B8%80%E5%AE%9A%E6%9C%83%E7%94%A8%E5%88%B0%E7%9A%84%E5%8E%9F%E5%9B%A0-%E5%85%B8"><thead><tr><th>類別</th><th>網站/組織</th><th>你一定會用到的原因</th><th>典型使用情境</th></tr></thead><tbody><tr><td>漏洞編號權威</td><td>**CVE.org (CVE Program)**</td><td>漏洞的全球標準「身分證」與最基本定義來源。([cve.org](https://www.cve.org/about/overview?utm_source=chatgpt.com "Overview - About the CVE Program"))</td><td>對齊漏洞名稱、內部通報、追蹤供應鏈依賴中的 CVE</td></tr><tr><td>漏洞資料庫</td><td>**NVD (NIST)**</td><td>把 CVE 補成可管理的資料：常含 CVSS、CWE、CPE 等，方便自動化對帳與風險排序。([NVD](https://nvd.nist.gov/?utm_source=chatgpt.com "NVD - Home - National Institute of Standards and Technology"))</td><td>漏洞嚴重度評估、資產比對、報告引用</td></tr><tr><td>已被實戰利用清單</td><td>**CISA KEV Catalog**</td><td>告訴你「這顆真的有人在野外利用」，修補優先級通常要直接拉到最前。([CISA](https://www.cisa.gov/known-exploited-vulnerabilities-catalog?utm_source=chatgpt.com "Known Exploited Vulnerabilities Catalog"))</td><td>Patch 優先級決策、對管理層/變更會說明</td></tr><tr><td>Web 安全共識</td><td>**OWASP Top 10**</td><td>Web 風險最常見的基線教材與治理共識；目前仍以 2021 為最新正式版、2025 有 RC 動態。([OWASP](https://owasp.org/www-project-top-ten/?utm_source=chatgpt.com "OWASP Top Ten Web Application Security Risks"))</td><td>安規教育、SDL 導入、WAF 規則與測試清單</td></tr><tr><td>攻擊技術知識庫</td><td>**MITRE ATT&amp;CK**</td><td>威脅模型/檢測與對應防禦的共同語言（戰術/技術）。([MITRE ATT&amp;CK](https://attack.mitre.org/?utm_source=chatgpt.com "MITRE ATT&CK®"))</td><td>SOC 規則對齊、紫隊演練、事件回溯與覆蓋率盤點</td></tr><tr><td>設定強化基線</td><td>**CIS Benchmarks / CIS**</td><td>各大產品/平台的共識型硬化建議，常用來做基線與稽核。([CIS](https://www.cisecurity.org/cis-benchmarks?utm_source=chatgpt.com "CIS Benchmarks®"))</td><td>OS/DB/Cloud 安全基線、合規與稽核</td></tr><tr><td>開源漏洞清單</td><td>**GitHub Advisory Database**</td><td>對開源套件很實用，能直接對應 registry（npm/pip/maven…）與漏洞資訊。([GitHub](https://github.com/advisories?utm_source=chatgpt.com "GitHub Advisory Database"))</td><td>供應鏈風險、依賴漏洞快篩、DevSecOps</td></tr><tr><td>公網暴露面搜尋</td><td>**Shodan**</td><td>用來找「連上網的設備/服務」的搜尋引擎，做外部暴露面盤點很直觀。([shodan.io](https://www.shodan.io/?utm_source=chatgpt.com "Shodan Search Engine"))</td><td>自家公網資產盤點、錯誤暴露偵測</td></tr><tr><td>公網掃描/盤點</td><td>**Censys**</td><td>以網際網路掃描為基礎的資產發現/監測平台，適合做攻擊面治理。([Censys](https://search.censys.io/?utm_source=chatgpt.com "Censys Search"))</td><td>對外服務監控、憑證/服務指紋觀測</td></tr><tr><td>惡意檔/URL 快篩</td><td>**VirusTotal**</td><td>多引擎/多情資的快速「第二意見」，適合初步 triage。([virustotal.com](https://www.virustotal.com/learn/?utm_source=chatgpt.com "VirusTotal – Learning resources"))</td><td>可疑檔案、URL、IP/domain 初判</td></tr><tr><td>外洩查詢</td><td>**Have I Been Pwned (HIBP)**</td><td>查帳號/網域是否出現在已知外洩事件，用於個人與企業風險提醒。([Have I Been Pwned](https://haveibeenpwned.com/?utm_source=chatgpt.com "Have I Been Pwned: Check if your email address has been ..."))</td><td>帳號風險告警、憑證填充（credential stuffing）防護前置</td></tr><tr><td>漏洞利用參考</td><td>**Exploit-DB**</td><td>公開 PoC/Exploit 索引，偏研究/測試用途；防守端可用來驗證「是否真的可利用」。([Exploit DB](https://www.exploit-db.com/?utm_source=chatgpt.com "Exploit Database - Exploits for Penetration Testers ..."))</td><td>修補驗證、風險評估佐證（注意合規與授權）</td></tr></tbody></table>

---

### 你可以怎麼把這份表用在日常流程

**一條龍最省腦的作法：**

1. 新漏洞出現 → **CVE.org** 對齊編號。([cve.org](https://www.cve.org/about/overview?utm_source=chatgpt.com "Overview - About the CVE Program"))
2. 去 **NVD** 看 CVSS/CPE/CWE 做初步排序與資產比對。([NVD](https://nvd.nist.gov/?utm_source=chatgpt.com "NVD - Home - National Institute of Standards and Technology"))
3. 檢查是否進 **CISA KEV** → 有就直接升級為最高修補優先。([CISA](https://www.cisa.gov/known-exploited-vulnerabilities-catalog?utm_source=chatgpt.com "Known Exploited Vulnerabilities Catalog"))
4. 開源套件再補查 **GitHub Advisory Database**。([GitHub](https://github.com/advisories?utm_source=chatgpt.com "GitHub Advisory Database"))
5. 對外暴露面用 **Shodan/Censys** 做「我家有沒有暴露且中版本」的交叉驗證。([shodan.io](https://www.shodan.io/?utm_source=chatgpt.com "Shodan Search Engine"))
6. 事件/偵測與演練對照 **MITRE ATT&amp;CK**。([MITRE ATT&amp;CK](https://attack.mitre.org/?utm_source=chatgpt.com "MITRE ATT&CK®"))
7. 長期治理用 **OWASP + CIS Benchmarks** 建基線。([OWASP](https://owasp.org/www-project-top-ten/?utm_source=chatgpt.com "OWASP Top Ten Web Application Security Risks"))

---

# 資安新聞

<table border="1" id="bkmrk-%E6%96%B0%E8%81%9E%E7%B6%B2%E7%AB%99%E5%90%8D%E7%A8%B1-rss-url-%E6%96%B0%E8%81%9E%E9%80%B1%E5%A0%B1%E5%8F%83" style="border-collapse: collapse; width: 100%; height: 313px;"><tbody><tr style="height: 28px;"><td style="width: 16.2346%; height: 28px;">新聞網站名稱</td><td style="width: 34.0122%; height: 28px;">RSS URL</td><td style="width: 14.7532%; height: 28px;">新聞週報參考性</td><td style="width: 35%; height: 28px;">備註</td></tr><tr style="height: 28px;"><td style="width: 16.2346%; height: 28px;">iThome 資安頻道</td><td style="width: 34.0122%; height: 28px;">[https://www.ithome.com.tw/rss/security](https://www.ithome.com.tw/rss/security)

</td><td style="width: 14.7532%; height: 28px;">主要來源</td><td style="width: 35%; height: 28px;">臺灣最大資安新聞</td></tr><tr style="height: 28px;"><td style="width: 16.2346%; height: 28px;">TWCERT/CC</td><td style="width: 34.0122%; height: 28px;">[https://www.twcert.org.tw/tw/rss-104-1.xml](https://www.twcert.org.tw/tw/rss-104-1.xml)</td><td style="width: 14.7532%; height: 28px;">主要來源</td><td style="width: 35%; height: 28px;">臺灣電腦網路危機處理技協調中心

著重於臺灣發生或是可能影響臺灣的資安事件新聞

</td></tr><tr style="height: 28px;"><td style="width: 16.2346%; height: 28px;">資安趨勢部落格

</td><td style="width: 34.0122%; height: 28px;">[http://blog.trendmicro.com.tw/?feed=rss2](http://blog.trendmicro.com.tw/?feed=rss2)

</td><td style="width: 14.7532%; height: 28px;">主要來源</td><td style="width: 35%; height: 28px;">有每週資安新聞彙整</td></tr><tr style="height: 28px;"><td style="width: 16.2346%; height: 28px;">The Hacker News

</td><td style="width: 34.0122%; height: 28px;">[http://thehackernews.com/feeds/posts/default](http://thehackernews.com/feeds/posts/default)

</td><td style="width: 14.7532%; height: 28px;">參考來源</td><td style="width: 35%; height: 28px;"> </td></tr><tr style="height: 28px;"><td style="width: 16.2346%; height: 28px;">FreeBuf

</td><td style="width: 34.0122%; height: 28px;">[http://www.freebuf.com/feed](http://www.freebuf.com/feed)

</td><td style="width: 14.7532%; height: 28px;">參考來源

技術文章

</td><td style="width: 35%; height: 28px;">有每日資安新聞彙整

有許多技術文章可以看來增進自己的技術知識

</td></tr><tr style="height: 28px;"><td style="width: 16.2346%; height: 28px;">技服中心

</td><td style="width: 34.0122%; height: 28px;">[http://www.nccst.nat.gov.tw/Services/FeedService.svc/GetFeed?lang=zh&amp;category=news&amp;format=rss](http://www.nccst.nat.gov.tw/Services/FeedService.svc/GetFeed?lang=zh&category=news&format=rss)

</td><td style="width: 14.7532%; height: 28px;">主要來源</td><td style="width: 35%; height: 28px;">國家資通安全會報技術服務中心

認為重要的新聞

</td></tr><tr style="height: 28px;"><td style="width: 16.2346%; height: 28px;">安全客-安全知识

</td><td style="width: 34.0122%; height: 28px;">[https://rsshub.app/aqk/knowledge](https://rsshub.app/aqk/knowledge)

</td><td style="width: 14.7532%; height: 28px;">技術文章</td><td style="width: 35%; height: 28px;"> </td></tr><tr style="height: 28px;"><td style="width: 16.2346%; height: 28px;">安全客-有思想的安全新媒体

</td><td style="width: 34.0122%; height: 28px;">[https://api.anquanke.com/data/v1/rss](https://api.anquanke.com/data/v1/rss)

</td><td style="width: 14.7532%; height: 28px;">參考來源</td><td style="width: 35%; height: 28px;"> </td></tr><tr style="height: 28px;"><td style="width: 16.2346%; height: 28px;">技服中心-漏洞警訊</td><td style="width: 34.0122%; height: 28px;">[https://www.nccst.nat.gov.tw/Services/FeedService.svc/GetFeed?lang=zh&amp;category=vulnerability&amp;format=rss](https://www.nccst.nat.gov.tw/Services/FeedService.svc/GetFeed?lang=zh&category=vulnerability&format=rss)

</td><td style="width: 14.7532%; height: 28px;">參考來源</td><td style="width: 35%; height: 28px;">國家資通安全會報技術服務中心

所通報認為對台灣影響嚴重的漏洞警訊

</td></tr><tr style="height: 33px;"><td style="width: 16.2346%; height: 33px;">F-ISAC</td><td style="width: 34.0122%; height: 33px;">[https://fisacs.tw](https://fisacs.tw) (這不是RSS)

</td><td style="width: 14.7532%; height: 33px;">主要來源</td><td style="width: 35%; height: 33px;">金融資安資訊分享與分析中心

</td></tr></tbody></table>

# Citrix 進行特定 User-Agent 的阻擋

如果需要在 Citrix ADC 上針對特定網站（例如 `api.aaa.com`）進行特定 `User-Agent` 的阻擋，可以基於虛擬服務 (Virtual Server) 或 `Host` 標頭進行精確匹配。以下是詳細步驟：

---

### **方法 1：基於虛擬服務 (Virtual Server)**

假設 `api.aaa.com` 綁定到一個特定的虛擬服務，則可以直接對該虛擬服務配置 Responder Policy 或 Rewrite Policy。

#### **步驟**

1. **找到虛擬服務**
    
    
    - 登入 Citrix ADC 管理界面。
    - 前往 **Traffic Management &gt; Load Balancing &gt; Virtual Servers**。
    - 找到綁定 `api.aaa.com` 的虛擬服務。
2. **創建 Responder Policy**
    
    
    - 前往 **AppExpert &gt; Responder &gt; Policies**。
    - 點擊 **Add**，輸入以下內容： 
        - **Policy Name**：例如 `BlockUserAgentForAPI`.
        - **Rule**： ```plaintext
            HTTP.REQ.HEADER("Host").EQ("api.aaa.com") && HTTP.REQ.HEADER("User-Agent").CONTAINS("某關鍵字")
            
            ```
        - **Action**：選擇 **DROP** 或 **Redirect**（例如引導到一個錯誤頁）。
3. **綁定到虛擬服務**
    
    
    - 編輯對應的虛擬服務。
    - 在 **Responder Policies** 部分，綁定剛創建的 Policy。
4. **測試效果**
    
    
    - 使用工具（如 `curl` 或 Postman），模擬包含匹配的 `User-Agent` 訪問 `api.aaa.com`，應該會被阻擋。

---

### **方法 2：基於 HTTP Host Header 的精確匹配**

如果 `api.aaa.com` 與其他應用共享同一虛擬服務，可以基於 `Host` 標頭進行條件判斷。

#### **步驟**

1. **創建 Responder Policy**
    
    
    - 配置條件如下： ```plaintext
        HTTP.REQ.HEADER("Host").EQ("api.aaa.com") && HTTP.REQ.HEADER("User-Agent").CONTAINS("某關鍵字")
        
        ```
    - 動作可以設為： 
        - **DROP**：直接丟棄請求。
        - **Redirect**：引導至一個錯誤頁面，例如： ```plaintext
            HTTP.REQ.URL.SET_TEXT_MODE("http://error-page.yourdomain.com")
            
            ```
2. **綁定到虛擬服務**
    
    
    - 在對應虛擬服務的 **Policies** 區域進行綁定。
3. **測試效果**

---

### **方法 3：使用 Rewrite 改寫或拒絕請求**

#### **改寫方式**

如果你不想丟棄請求，而是改變特定 User-Agent 的行為，可以用 Rewrite Policy 改寫請求，例如修改 `User-Agent` 為通用值或添加額外標頭：

1. **Rewrite Policy 條件**
    
    ```plaintext
    HTTP.REQ.HEADER("Host").EQ("api.aaa.com") && HTTP.REQ.HEADER("User-Agent").CONTAINS("某關鍵字")
    
    ```
2. **Rewrite Action**
    
    
    - 替換 `User-Agent`： ```plaintext
        HTTP.REQ.HEADER("User-Agent").SET_TEXT("DefaultUserAgent")
        
        ```
    - 添加自定義標頭： ```plaintext
        HTTP.REQ.HEADER("X-Blocked").SET_TEXT("Blocked")
        
        ```
3. **綁定到虛擬服務**

---

### **方法 4：使用 Application Firewall (更高級別保護)**

若已啟用 Application Firewall，可基於 `Host` 和 `User-Agent` 配置策略：

1. **創建 Application Firewall Profile**
    
    
    - 添加一個自定義 Profile，啟用 **Signatures**。
    - 創建自定義簽名： 
        - **名稱**：例如 `BlockUAForAPI`
        - **條件**： ```plaintext
            HTTP.REQ.HEADER("Host").EQ("api.aaa.com") && HTTP.REQ.HEADER("User-Agent").CONTAINS("某關鍵字")
            
            ```
2. **綁定 Profile 至虛擬服務**

---

### 測試命令範例

可以使用以下命令測試效果：

```bash
curl -H "Host: api.aaa.com" -H "User-Agent: 特定關鍵字" http://<你的 ADC IP>/path

```

這將模擬請求，並確認是否阻擋成功。

---

如果有更多需求或複雜場景，可以提供更多細節，我將協助進一步優化配置！

# 【CVE】CVE相關

[https://www.cve.org/](https://www.cve.org/)  
[https://nvd.nist.gov/vuln/detail/CVE-2025-55182](https://nvd.nist.gov/vuln/detail/CVE-2025-55182)

[![image-1765263221534.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-12/scaled-1680-/4sKc4bJ7JJm9dweL-image-1765263221534.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-12/4sKc4bJ7JJm9dweL-image-1765263221534.png)

我用「照著你這張圖從上到下」的方式，帶你看**每一塊在講什麼、要怎麼解讀、你實務上該拿哪幾段去做判斷**。這張圖看起來就是 **NVD 的 CVE 詳細頁**截圖。

---

[![image-1765263850143.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-12/scaled-1680-/S7bqup7ZiSYtlUyR-image-1765263850143.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-12/S7bqup7ZiSYtlUyR-image-1765263850143.png)

## 1) 標題區：你現在在看哪一個漏洞

最上面：**CVE-2025-55182 Detail**

- 這就是漏洞的**唯一身分證**。
- 你後續要對內通報、對外追蹤、查 SBOM/掃描報告，都用這個編號對齊。

---

## 2) Description：用一句到兩句話告訴你「這是什麼」

你圖上的描述大意是：

- **React Server Components 的 pre-auth RCE**
- 影響 **React 19.0.0 / 19.1.0 / 19.1.1 / 19.2.0**
- 相關套件含  
    `react-server-dom-parcel / turbopack / webpack`
- 根因屬於**不安全反序列化**  
    （從 HTTP 請求 payload 進到 Server Function endpoints）

你可以把這段當成第一層判讀：

**「未登入就可能打 RCE」**  
→ 這種通常在企業環境都是**最高優先級**。

---

[![image-1765263899408.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-12/scaled-1680-/GFt0LgrySzUALMxx-image-1765263899408.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-12/GFt0LgrySzUALMxx-image-1765263899408.png)

## 3) Metrics / CVSS 分頁：看嚴重度分數與誰給的

這區通常有三個 tab：

- **CVSS v4.0**
- **CVSS v3.x**
- **CVSS v2.0**

你截圖目前是在 **CVSS v3.x** 的畫面。

### 你圖上有兩條來源很重要：

#### (A) NIST: NVD

- **Base Score: N/A**
- 旁邊寫 **NVD assessment not yet provided**

意思：  
**NVD 官方還沒完成自己的評分**。

#### (B) CNA: Facebook, Inc.

- **Base Score: 10.0 CRITICAL**
- **Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H**

意思：  
**由 CNA（這裡是 Facebook/Meta）先提供了分數與向量**。  
所以你現在可以先用這個做風險判斷。

---

## 4) 看懂 CVSS 3.1 向量（你圖上那串）

這串：

`AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H`

你可以用一句白話解：

- **AV:N**：可從網路遠端打
- **AC:L**：攻擊條件不難
- **PR:N**：不用登入
- **UI:N**：不用使用者點擊
- **S:C**：影響可跨安全邊界
- **C/I/A 都是 H**：機密性/完整性/可用性皆高衝擊

所以這組合很典型就是：  
**「高可利用性 + 高破壞性」**  
→ **10 分合理**。

---

## 5) References to Advisories, Solutions, and Tools

這張表是**你實務上最常點的區塊**。

你會看到：

- **Source(s)**
    
    
    - CVE
    - CISA-ADP
    - Facebook, Inc.
    - US Government Resource
- **Tag(s)**
    
    
    - Mailing List
    - Patch
    - Third Party Advisory
    - Vendor Advisory
    - Issue Tracking

### 你可以這樣用：

- **要找「原廠修補與版本建議」**  
    → 看 **Vendor Advisory / Patch** 那些列
- **要看「是否已被利用、政府觀點」**  
    → 看 **CISA** 相關列
- **要找「社群討論/觀測」**  
    → Issue Tracking / Mailing List

---

## 6) “This CVE is in CISA’s KEV Catalog”

這段超關鍵。

你圖上直接寫：

**This CVE is in CISA’s Known Exploited Vulnerabilities Catalog**

底下表格有：

- **Date Added**（加入日期）
- **Due Date**（要求修補期限）
- **Required Action**（該做什麼）

白話解讀：

> **這顆不是「可能會被打」  
> 是「已經在野外有人拿來打」**  
> 所以 CISA 直接要求你在期限前完成修補或緩解。

這一段就是你對管理層/變更委員會最好用的「加速器」。

---

## 7) Weakness Enumeration（CWE）

你圖上是：

- **CWE-502: Deserialization of Untrusted Data**

它是在告訴你**漏洞類型家族**：

> **不安全反序列化**

這對你做：

- 長期治理
- Code Review 規範
- SAST 規則
- WAF/Runtime 防護策略

都很有用。

---

## 8) Known Affected Software Configurations（CPE）

這區會列：

- **Configuration 1**：例如 React 的 CPE
- **Configuration 2**：看起來也列到了 Next.js 的 CPE（含 canary 與版本範圍）

你要注意兩件事：

### (A) 這是「NVD 的結構化影響清單」

它方便你：

- 用資產清冊 / 弱掃工具
- 做**版本比對與自動化對帳**

### (B) CPE 有時會「過度或滯後」

尤其遇到：

- 框架/套件相依複雜
- canary/nightly
- 多層依賴

**所以最終修補版本與實際影響判斷**  
仍要回頭以：

- **原廠公告**
- **你專案的 lockfile / SBOM**

做最後確認。

---

## 9) 你要怎麼用這張頁面做「實務決策」

### 你可以用這個超短流程：

1. **看 Description**
    
    
    - 確認類型：pre-auth RCE？
2. **看 CVSS**
    
    
    - 即便 NVD 還沒評分，也先用 CNA 分數
3. **看 KEV 有沒有上榜**
    
    
    - 有 → 直接拉到最高優先
4. **看 References 的 Vendor Advisory / Patch**
    
    
    - 找**正確修補版本**
5. **看 CPE**
    
    
    - 跟你環境做快速自動比對
6. **回到你專案依賴**
    
    
    - `package-lock.json / yarn.lock / pnpm-lock.yaml`
    - SBOM
    - Runtime 掃描

---

## 一句話總結你這張圖的重點

- **這顆是高風險 pre-auth RCE**
- **CNA 已先給 CVSS 10**
- **而且已進 KEV（表示已被實戰利用）**
- **CWE 指向不安全反序列化**
- **References 用來找原廠修補**
- **CPE 提供自動對帳，但要以原廠公告與實際依賴確認**

# 【名詞解釋】CPE - 軟體/硬體/作業系統的標準化身分證

CPE 可以把它想成\*\*「軟體/硬體/作業系統的標準化身分證」\*\*，用來讓不同的弱掃、資產盤點、漏洞資料庫能用同一種語言描述「到底是哪個產品、哪個版本」。

---

## CPE 是什麼

**CPE（Common Platform Enumeration）** 是 NIST 定義的一套命名標準。  
它用**統一格式**描述：

- 產品類型（應用程式 / 作業系統 / 硬體）
- 廠商
- 產品名稱
- 版本
- 其他屬性（語言、更新版、版次…）

**目的：**  
讓「漏洞（CVE）」可以清楚綁到「受影響產品/版本」。

---

## 你會在哪裡看到 CPE

最常見的地方：

- **NVD 的 CVE 詳細頁**  
    下面會有 **Known Affected Software Configurations**  
    就是用 CPE 寫出「哪些產品版本受影響」。
- **弱點掃描/資產管理工具**  
    用 CPE 來做自動比對與風險盤點。

---

## CPE 2.3 長什麼樣（看得懂就好）

你常見的是 **CPE 2.3** 格式：

`cpe:2.3:<part>:<vendor>:<product>:<version>:...`

其中 `<part>` 通常是：

- `a` = application（應用）
- `o` = operating system（作業系統）
- `h` = hardware（硬體）

（後面還有很多欄位，但實務上你最常看前 4-5 個就夠了。）

---

## 為什麼 CPE 很重要（實務角度）

有 CPE，你就能做到：

1. **自動化對帳**
    
    
    - 你的資產清冊 / SBOM / 掃描結果
    - 跟 NVD 的受影響清單做比對  
        → 快速找出「我是不是中招」。
2. **跨工具一致性**
    
    
    - 不同廠牌的掃描器
    - 不同資料來源  
        都能用同一套識別方式對齊。

---

## 但 CPE 也有現實限制

這點很關鍵，避免誤判：

- **更新可能慢半拍或不精準**  
    新框架、新套件或複雜相依  
    CPE 可能不完整或延後補齊。
- **對前端/開源生態有時不夠細**  
    特別是
    
    
    - 子套件
    - monorepo
    - canary/nightly
    - 特殊建置版本

所以最佳做法是：

> **CPE 用來快速盤點**  
> **最終以原廠公告 + 你的 lockfile/SBOM 驗證**

---

## 一句超白話總結

**CPE = 讓漏洞資料庫能精準說「哪個產品/哪個版本」的標準名稱。**  
它是 CVE 與你的資產之間的**重要橋樑**。

# 【名詞解釋】NIST 美國國家標準與技術研究院

NIST 是**美國國家標準與技術研究院**（National Institute of Standards and Technology）。  
你可以把它理解成一個**替政府與產業制定/維護技術標準與安全指引的權威機構**。

在資安領域你最常見到 NIST 的地方：

- **NVD**（National Vulnerability Database）  
    NIST 維護的漏洞資料庫，收錄 CVE、CPE、CVSS 等資訊。
- **NIST Cybersecurity Framework (CSF)**  
    很多企業用來做資安治理與成熟度規劃的框架。
- **SP 800 系列**  
    一堆實務很常引用的安全指南（存取控制、風險管理、零信任等）。

一句話：  
**NIST 就是美國最重要的「技術與資安標準制定/指引中心」之一**，  
很多全球通用的漏洞與資安管理做法都會跟它有關。

# 【名詞解釋】CVE - 漏洞的全球統一編號/身分證

CVE 是\*\*「通用漏洞與曝露」**（Common Vulnerabilities and Exposures），你可以把它想成**漏洞的全球統一編號/身分證\*\*。

### 它在做什麼？

- 給每個已知漏洞一個**唯一 ID**  
    例：`CVE-2025-55182`
- 讓所有人（廠商、研究員、弱掃工具、SOC、SRE）  
    **用同一個名字講同一個漏洞**，避免混亂。

### CVE 本身包含什麼？

通常是：

- 基本描述
- 參考連結
- 由哪個 CNA（負責登錄的單位）提交

**但 CVE 不一定會有完整的風險評分或受影響產品細節。**  
更完整的整理常會去看：

- **NVD**（補充 CVSS、CPE 等）

### 一句話

**CVE = 漏洞的標準化命名與編號系統**，用來統一追蹤與溝通。

# 【名詞解釋】CNA - CVE 編號授權/編號發放單位

CNA 是 **CVE Numbering Authority** 的縮寫，中文可以理解成\*\*「CVE 編號授權/編號發放單位」\*\*。

---

## 白話版

**CNA 就是被官方授權可以：**

1. **分配 CVE 編號**
2. **撰寫/提交該漏洞的官方描述與資料**

所以很多 CVE 是由各大廠或重要開源組織自己登記的。

---

## 你會在哪裡看到 CNA

在 NVD 或 CVE 詳細頁常會看到：

- **CNA: Microsoft / Google / Apple / Meta / Red Hat / Debian...**  
    代表這顆 CVE 是由哪個單位負責登錄與提供初始資訊。

---

## 為什麼重要

- **可信度與速度**：  
    原廠/專案當 CNA 往往最了解影響範圍與修補版本。
- **你在看 CVSS 時要注意**：  
    可能會先看到 **CNA 提供的分數**，  
    稍後才補上 **NVD/NIST 的評分**。

---

## 一句話總結

**CNA = 被授權負責「發 CVE 編號 + 提交漏洞官方資訊」的單位。**

# 【名詞解釋】CVSS 通用漏洞評分系統

CVSS 是 **Common Vulnerability Scoring System**，中文常翻 **「通用漏洞評分系統」**。  
你可以把它理解成：**用一套標準公式，把漏洞危險程度量化成分數（0~10）**，方便你排序修補優先級。

---

## 它解決什麼問題？

同一個漏洞有人說很嚴重、有人說還好。  
CVSS 讓大家用**同一把尺**衡量。

---

## 分數怎麼看？

一般會看到這種分類：

- **0.1–3.9：Low（低）**
- **4.0–6.9：Medium（中）**
- **7.0–8.9：High（高）**
- **9.0–10.0：Critical（重大）**

---

## 你最常看到的是「CVSS v3.1 向量」

像這樣：

`AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H`

白話解讀：

- **AV:N**：可遠端網路攻擊
- **AC:L**：攻擊難度低
- **PR:N**：不需要登入權限
- **UI:N**：不需要使用者操作
- **C/I/A**：機密性/完整性/可用性影響程度

<table id="bkmrk-%E6%AC%84%E4%BD%8D-%E4%BD%A0%E8%A6%81%E7%9C%8B%E4%BB%80%E9%BA%BC-%E4%BB%A3%E7%A2%BC-%E2%86%92-%E7%99%BD%E8%A9%B1-av-"><thead><tr><th>欄位</th><th>你要看什麼</th><th>代碼 → 白話</th></tr></thead><tbody><tr><td>**AV** 攻擊途徑</td><td>**能不能遠端打**</td><td>**N** 網路遠端（最危） / **A** 鄰接網路 / **L** 本機 / **P** 實體</td></tr><tr><td>**AC** 攻擊複雜度</td><td>**好不好打**</td><td>**L** 好打 / **H** 難打</td></tr><tr><td>**PR** 需要權限</td><td>**要不要登入/權限**</td><td>**N** 不用 / **L** 低權限 / **H** 高權限</td></tr><tr><td>**UI** 使用者互動</td><td>**要不要人配合**</td><td>**N** 不用點擊 / **R** 要使用者操作</td></tr><tr><td>**S** 影響範圍</td><td>**會不會跨邊界**</td><td>**U** 不跨 / **C** 跨（通常會拉高嚴重度）</td></tr><tr><td>**C/I/A** 影響面</td><td>**被打中會多慘**</td><td>**N** 無 / **L** 低 / **H** 高（機密/完整/可用）</td></tr></tbody></table>

---

## 實務上怎麼用最有效？

你可以這樣做排序：

1. **先看 CVSS** 判斷技術嚴重度
2. **再看 KEV**
    
    
    - 有進 KEV → 代表已在野外被利用 → 直接拉到最前面
3. 加上你自己的環境因素
    
    
    - 是否對外暴露
    - 是否核心資產
    - 是否有補償控制

---

## 一句話

**CVSS = 幫你把漏洞風險「用 0~10 分標準化量化」的系統**，  
用來做修補優先級與風險溝通。

---

## CVSS vXX向量 如何解讀

你可以把 **CVSS 向量**想成一串「漏洞特性設定值」，每個欄位都對應一個風險面向。  
**解讀方式就是：把每個縮寫拆開 → 看它選了哪個值 → 對應官方定義。**

下面我用你最常會遇到的 **v3.1** 和新一代 **v4.0** 來示範，最後告訴你**參數去哪裡查**。

---

## 怎麼解讀 CVSS v3.1 向量

典型長相：  
`CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H`

你可以照這個順序讀：

### Base Metrics（最常用、最重要）

- **AV (Attack Vector)** 攻擊途徑
    
    
    - N 網路、A 鄰接網路、L 本機、P 實體
- **AC (Attack Complexity)** 攻擊複雜度
    
    
    - L 低、H 高
- **PR (Privileges Required)** 需要權限
    
    
    - N 無、L 低、H 高
- **UI (User Interaction)** 需要使用者互動
    
    
    - N 不需要、R 需要
- **S (Scope)** 影響範圍是否跨安全邊界
    
    
    - U 不變、C 改變
- **C / I / A**
    
    
    - **Confidentiality / Integrity / Availability**  
        影響機密性/完整性/可用性
    - N 無、L 低、H 高

### 你可以用一個超快口訣理解

- **AV:N + PR:N + UI:N + AC:L**  
    幾乎就是「**遠端、免登入、免點擊、好打**」  
    → 分數通常會非常高。

---

## CVSS v4.0 有什麼不一樣？

v4.0 把 v3.x 一些模糊處拆更細，  
更強調**可重現性**、**真實情境的細節**。

你可能會看到類似：

`CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H`

### 你會注意到幾個新或改良概念

- **AT (Attack Requirements)** 攻擊前提/條件
    
    
    - N 無特殊前提、P 需要特定前提  
        用來補足 v3 的 AC 在某些場景太粗的問題。
- **把影響拆成兩組：**
    
    
    - **V** = Vulnerable system（受害系統本體）
        
        
        - **VC/VI/VA**
    - **S** = Subsequent system（後續/相連系統）
        
        
        - **SC/SI/SA**  
            這樣能更清楚描述「**我打 A 但連帶害到 B**」的情形。

---

## 那 CVSS v2.0 呢？

你可能還會在老系統看到：

`AV:N/AC:L/Au:N/C:C/I:C/A:C`

- **Au (Authentication)** 舊版用來描述認證需求  
    v3 之後改成 PR/UI/S 等更細的拆法。

---

## 你要去哪裡查「每個參數的官方定義」？

### 1) FIRST 官方文件（最權威）

CVSS 的規格與定義是由 **FIRST** 維護。  
你要查：

- 每一版（v2 / v3.1 / v4.0）的欄位定義
- 各數值代表的精確意義
- 計分邏輯

### 2) 官方計算器（最好用）

用計算器的好處：

- 你可以**勾選** AV/AC/PR/UI…
- 工具會自動生成向量與分數
- 同時會顯示每個選項的文字說明

你可以用：

- **FIRST 的 CVSS 計算器**
- **NVD 的 CVSS 計算器**

### 3) NVD 的 CVE 詳細頁

NVD 頁面上會直接列：

- **向量字串**
- **Base Score**
- 有時會同時列 **CNA** 與 **NVD** 的評分  
    適合你快速對照。

---

## 一個實務上超好用的解讀流程

1. 先看版本
    
    
    - `CVSS:3.1` 或 `CVSS:4.0`
2. 先抓「好不好打」
    
    
    - **AV / AC / PR / UI**  
        如果看到  
        **AV:N + PR:N + UI:N + AC:L**  
        → 先把它當高優先級候選。
3. 再看「影響多大」
    
    
    - v3.x 看 **C/I/A**
    - v4.0 看 **VC/VI/VA** + **SC/SI/SA**
4. 最後再加上你自己的環境因素
    
    
    - 是否對外
    - 是否核心資產
    - 有無 WAF/隔離/補償控制
    - **是否進 KEV**（有的話優先級再往前）

---

## 你可以直接套用的白話翻譯模板

看到一串向量，你可以這樣快速寫成一句話：

> 這個漏洞可以 **（AV）** 從 **（攻擊途徑）** 發動，  
> 攻擊難度 **（AC）**，  
> 不需要/需要 **（PR/UI）**，  
> 影響 **（C/I/A 或 VC/VI/VA）**，  
> 是否跨安全邊界 **（S 或 v4 的後續系統影響）**。

---

如果你願意，我可以拿你們實際遇到的一條向量（你貼一串就好），  
幫你**逐欄位翻成中文 + 寫成 1-2 句可放事故/風險報告的描述**。

# 【名詞解釋】KEV 已知遭利用漏洞清單

KEV 是 **Known Exploited Vulnerabilities** 的縮寫，通常指 **CISA 的「已知遭利用漏洞清單（KEV Catalog）」**。

你可以把它當成：

> **「這些漏洞不是理論風險，是真的已經有人在野外拿來打了」的名單。**

---

## KEV 用來做什麼

- 幫你快速判斷**修補優先級**  
    只要某個 CVE 進了 KEV，通常就要**優先處理**。
- 對管理層／變更審查很有說服力  
    因為它代表**有實際攻擊證據**。

---

## KEV 清單常見欄位（你會看到）

- **CVE ID**
- **Vendor / Product**
- **Vulnerability Name**
- **Date Added**（被列入日期）
- **Due Date**（建議或要求完成修補的期限）
- **Required Action**（要修補或採取緩解措施）

---

## 一句話實務規則

**CVSS 告訴你「多危險」**，  
**KEV 告訴你「已經有人在用這顆打人」**。

所以實務排序常會是：

> **KEV 優先於單看 CVSS。**

---

# 【名詞解釋】NVD 美國國家漏洞資料庫

NVD 是 **National Vulnerability Database（美國國家漏洞資料庫）**，由 **NIST** 維護。

你可以把它想成 **「CVE 的進階資料站」**：

- **CVE**：漏洞的編號與基本描述（像身分證）
- **NVD**：把這顆漏洞補上更完整、可用來做風險管理的資訊

---

## NVD 會提供什麼

常見會看到：

- **CVSS 分數與向量**（幫你判斷嚴重度）
- **CWE**（漏洞類型）
- **CPE**（受影響產品/版本的標準化描述）
- 參考連結、修補資訊（視資料完整度）

---

## 你何時會用到

- 新漏洞出現，要快速判斷**嚴重度與影響範圍**
- 你有資產清冊/SBOM，要用 **CPE** 做自動比對
- 寫內部通報或風險報告需要**權威來源**

---

一句話：  
**NVD = NIST 維護的漏洞百科，讓 CVE 變得可評分、可對帳、可管理。**

# 網路相關

# 【Cisco】Switch相關指令

```
LAB 2-1:Switch基本環境設定

清除Switch的組態設定
Switch>enable 
Switch#erase startup-config 
Switch#delete vlan.dat
Switch#reload

Switch設主機名稱
Switch#configure terminal 
Switch(config)#hostname lab2

console閒置設0分,啟用logging訊息輸出後自動換行功能
lab2(config)#line console 0
lab2(config-line)#exec-timeout 0  (閒置登出)
lab2(config-line)#logging synchronous  (訊息同步)

停用網域名稱反查
lab2(config-line)#exit
lab2(config)#no ip domain-lookup

設定界面資訊(description),雙工(duplex)模式及介面速度(speed)
lab2(config)#interface fastEthernet 0/12
lab2(config-if)#description TORouter
lab2(config-if)#duplex auto (預設auto)
lab2(config-if)#speed auto  (預設auto)
lab2(config-if)#exit

設定Switch IP及Gateway (L2 Switch訂IP管理用)
lab2(config)#interface vlan 1
lab2(config-if)#ip address 192.168.0.250 255.255.255.0
lab2(config-if)#no shutdown 
lab2(config-if)#exit
lab2(config)#ip default-gateway 192.168.0.254 (跨網段管理)

show 相關設定,存入NVRAM
lab2#show running-config 
lab2#show vlan 1
lab2#show interfaces fastEthernet 0/12
lab2#copy running-config startup-config 
LAB 2-2:Switch安全防護設定

設定Console及vty密碼
lab2#configure terminal 
lab2(config)#line console 0
lab2(config-line)#password console
lab2(config-line)#login 
lab2(config-line)#exit
lab2(config)#line vty 0 15
lab2(config-line)#password vty
lab2(config-line)#login
lab2(config-line)#exit

設定privileged mode的密碼並檢視
lab2(config)#enable password password
lab2(config)#enable secret cisco
lab2(config)#end
lab2#sh running-config

設定SSH
lab2#configure terminal 
lab2(config)#ip domain-name example.com
lab2(config)#crypto key generate rsa 
lab2(config)#username netadmin password netadmin
lab2(config)#ip ssh version 2
lab2(config)#line vty 0 15
lab2(config-line)#login local
lab2(config-line)#transport input ssh telnet

直接進入特權模式
lab2(config)#no username {netadmin}
lab2(config)#username {name} privilege 15 password {password}

啟用密碼加密服務
lab2#conf t
lab2(config)#service password-encryption 
lab2(config)#do show running-config | begin line

設定Login banner
lab2(config)#banner login >
  HI !!!! >
LAB 2-3:Port Security(防非法MAC存取)

 

Port security 只能針對Access mode的Port設定
enable port security
(Optional)設定最大MAC數量,預設 1 sh不顯示,Cisco設備預設值通常不顯示
違規時switch的動作violation{ shutdown預設 | restrict | protect}
1~4只防阻MAC Flooding攻擊,每一個port只能有1個MAC通過,但還不能防止非法MAC接到設備上,把合法MAC寫到table,只要MAC有錯port shutdown
在Switch上查看MAC-Address-Table
lab2#show mac-address-table (有些版本 show mac address-table)

將FA0/24啟用Port-Security功能,指定只學習一筆MAC資訊,使用Sticky方式學習到PC的MAC,並設定違規處理方式為Shutdown
lab2#configure terminal 
lab2(config)#interface fastEthernet 0/24  (多個Port可用range)
lab2(config-if)#switchport mode access  (將Port改成Access Mode)
lab2(config-if)#switchport port-security  (啟用Port Security)
lab2(config-if)#switchport port-security maximum 1  (設定最大的MAC數量)
lab2(config-if)#switchport port-security mac-address sticky 
lab2(config-if)#switchport port-security violation shutdown(預設,處理err-disabled)
或
lab2(config-if)#switchport port-security violation restrict  (合法MAC接回就會通)


range 多個Port要設定
lab2(config)#interface range fastEthernet 0/4 - 7 , fastEthernet 0/9 - 11
lab2(config-if-range)#switchport mode accesss

檢查所有界面的狀態(Port,Description Name,Status,Vlan,Duplex,Speed,Type)
lab2#sh interfaces status

檢查Switch上有哪些Port啟用Port-security
lab2(config-if)#do sh port-security

檢查Switch上有哪些Port有sticky或手動輸入合法MAC Address
lab2(config-if)#do sh port-security address

檢查Fa0/24的Port-security的詳細設定
lab2(config-if)#do sh port-security interface fastethernet 0/24

檢查Fa0/24的介面狀態(注意err-disabled)
lab2(config-if)#do sh int fa 0/24
            

將Fa0/24接另一台PC,因為Fa0/24有啟用Port-security,並將之前自動學習到的MAC以Sticky記錄,檢查Fa0/24的介面狀態會出現err-disabled:以下處理方法
lab2#sh int fa 0/24
FastEthernet0/24 is down, line protocol is down (err-disabled)
lab2#sh run | begin FastEthernet0/24
!
interface FastEthernet0/24
 switchport mode access
 switchport port-security
 switchport port-security mac-address sticky 
 switchport port-security mac-address sticky 0030.F22D.9A05
!
lab2#conf t
lab2(config)#int fa 0/24
lab2(config-if)#no switchport port-security mac-address sticky 0030.F22D.9A05
lab2(config-if)#do sh int fa 0/24
FastEthernet0/24 is down, line protocol is down (err-disabled)
lab2(config-if)#shutdown 
lab2(config-if)#no shutdown 
LAB 2-4:CDP與檔案備份練習

CDP查詢鄰近設備
lab2#sh cdp neighbors  (Device ID =>ro)
lab2#sh cdp entry ro

將Switch的fa0/3介面CDP功能關閉
lab2(config)#int fa 0/3
lab2(config-if)#no cdp enable

將設備dcp功能關閉
lab2(config)#no cdp run

將Switch的IOS與startup-config複製到TFTP
lab2#sh flash
lab2#copy flash tftp
lab2#copy running-config startup-config 
lab2#copy startup-config tftp

 

show 的使用
lab2#sh running-config | begin line
lab2#sh running-config | include line
lab2#sh interfaces status
lab2#sh mac address-table
#
#
#
```

# 【Fiddler】玩轉 Fiddler－HTTP(s) 抓包能手

# [玩轉 Fiddler－HTTP(s) 抓包能手 &amp; 常見「特殊」用途](https://ryanlee.tw/2021/08/23/fiddler/)

[https://ryanlee.tw/2021/08/23/fiddler/](https://ryanlee.tw/2021/08/23/fiddler/)

# 【Mac】透過console連接網路設備

需求：一條usb - console 線

[![image-1656036189511.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-06/scaled-1680-/6FeDtIIUNCCWS8hW-image-1656036189511.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-06/6FeDtIIUNCCWS8hW-image-1656036189511.png)

接上後，打開終端機，使用screen 登入設備

```shell
#找到 console 線
ll /dev/tty.*
crw-rw-rw-  1 root  wheel    9,   0  6 22 22:26 /dev/tty.Bluetooth-Incoming-Port
crw-rw-rw-  1 root  wheel    9,   2  6 24 09:58 /dev/tty.usbserial-A50285BI

#登入設備
screen /dev/tty.usbserial-A50285BI 9600
```

---

### screen其他操作

##### 退出後重連失敗

[![image-1656038788632.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-06/scaled-1680-/cllKnxGHBiZhc1Jj-image-1656038788632.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-06/cllKnxGHBiZhc1Jj-image-1656038788632.png)

```shell
#找到screen pid
screen -list

There are screens on:
        15520.ttys004.Treeman-Mac       (Attached)
        9100.ttys003.Treeman-Mac        (Detached)
2 Sockets in /var/folders/px/8z7kvlwj5278qcf92c71q6_h0000gn/T/.screen.

#方法一 刪除socket 重新連線
#刪除sccket screen -X -S {pid} quit
# -X: 執行cmd, -S: screen socket name, quit: 離開
screen -X -S 9100.ttys003.Treeman-Mac quit
#在連線一次
screen /dev/tty.usbserial-A50285BI 9600

#方法二 使用screen pid.name重新連線
screen -R 9100.ttys003.Treeman-Mac

#方法三 重新連線上次中斷screen
screen -r
```

# 【zabbix】自定義通知(轉貼)

 開啟 https://notify-bot.line.me/zh\_TW/

登入帳號後，下拉選項，選取「個人頁面」→「發行權杖」→「透過1對1聊天接收LINE Notify的通知」

之後就會得到一組權杖「Token」，這組Token務必要記錄下來，出現後便不再顯示

<div id="bkmrk-zabbix-server%E7%AB%AF">Zabbix Server端</div> **設定Scritp**

`sudo vi /usr/lib/zabbix/alertscripts/line_notify.sh`

<table border="1" cellpadding="1" cellspacing="1" id="bkmrk-%23%21%2Fbin%2Fbash%23-line-no"><tbody><tr><td>\#!/bin/bash  
\# LINE Notify Token - Media &gt; "Send to".  
TOKEN="$1"

\# {ALERT.SUBJECT}  
subject="$2"

\# {ALERT.MESSAGE}  
message="$3"

curl https://notify-api.line.me/api/notify -H "Authorization: Bearer ${TOKEN}" -F "message=${message}"

</td></tr></tbody></table>

`sudo chmod 755 /usr/lib/zabbix/alertscripts/line_notify.sh<br></br>sudo chown zabbix:zabbix line_notify.sh`

<div id="bkmrk-zabbix-ui-%E8%A8%AD%E5%AE%9A">Zabbix Ui 設定</div>**1. 「管理」→「示警媒介類型」→「創建示警媒介類型」**

[![image-1656924835958.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/scaled-1680-/ZpS1LqW4Q8srpH7v-image-1656924835958.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/ZpS1LqW4Q8srpH7v-image-1656924835958.png)

Name：Line Notify  
Type：Script  
Script name：line\_notify.sh  
Script parameters：  
{ALERT.SENDTO}  
{ALERT.SUBJECT}  
{ALERT.MESSAGE}

**2.「管理」→「用戶」→「Admin」→「示警媒介」→「新增」**

[![image-1656924854811.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/scaled-1680-/SWH7ljpOvKazuqGv-image-1656924854811.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/SWH7ljpOvKazuqGv-image-1656924854811.png)

Send to：填入上面步驟拿到的ˊTOKEN

**3.「組態」→「動作」→「創建動作」**

[![image-1656925027773.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/scaled-1680-/FRLGArWWJ8MlS9IG-image-1656925027773.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/FRLGArWWJ8MlS9IG-image-1656925027773.png)

<table border="1" cellpadding="1" cellspacing="1" id="bkmrk-%7Bhost.name1%7D%3A-%7Btrigg"><tbody><tr><td>{HOST.NAME1}: {TRIGGER.STATUS}: {TRIGGER.NAME}</td></tr></tbody></table>

<table border="1" cellpadding="1" cellspacing="1" id="bkmrk-%E4%B8%BB%E6%A9%9F%E5%90%8D%E7%A8%B1%3A-%7Bhostname1%7D%E7%99%BC%E7%94%9F%E6%99%82"><tbody><tr><td>主機名稱: {HOSTNAME1}  
發生時間: {EVENT.DATE} {EVENT.TIME}  
警示等級: {TRIGGER.SEVERITY}  
警示訊息: {TRIGGER.NAME}  
警示項目: {TRIGGER.KEY1}  
問題說明: {ITEM.NAME}: {ITEM.VALUE}  
當前狀態: {TRIGGER.STATUS}: {ITEM.VALUE1}  
事件ID: {EVENT.ID}</td></tr></tbody></table>

備註：**Recovery operations** 的欄位主旨和訊息都和這邊一樣

[![image-1656925062406.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/scaled-1680-/Rb864yoX03cXFsJe-image-1656925062406.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/Rb864yoX03cXFsJe-image-1656925062406.png)

[![image-1656925084845.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/scaled-1680-/ooN5dFnxHY2U45aD-image-1656925084845.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/ooN5dFnxHY2U45aD-image-1656925084845.png)

Recovery operations 的操作配置

[![image-1656925100227.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/scaled-1680-/ntcd5VSZGqWifrC6-image-1656925100227.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/ntcd5VSZGqWifrC6-image-1656925100227.png)

---

**配置完成後，進行測試**

將監控主機關機，五分鐘後，LINE就會進行報警

[![image-1656925113010.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/scaled-1680-/8vfTYxmODyCHQsCg-image-1656925113010.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/8vfTYxmODyCHQsCg-image-1656925113010.png)

出處：[﹝Linux﹞Zabbix LINE Notify 警報通知 | 工程師的江湖 - 點部落 (dotblogs.com.tw)](https://dotblogs.com.tw/xerion30476/2019/08/28/153643)

# 學習相關連結

**[《网络工程师的Python之路》教学文章、视频汇总 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/486260131)**

# smokeping install

```
timedatectl set-timezone Asia/Taipei
```

install docker

```
curl -fsSL https://get.docker.com | bash -s docker
```

run smokeping in docker

```
mkdir -p /home/monitor/smokeping/config
mkdir -p /home/monitor/smokeping/data
chown -R monitor /home/monitor/smokeping/

systemctl enable docker
systemctl start docker

docker pull linuxserver/smokeping
docker run -d \
--name=smokeping \
-e PUID=1000 \
-e PGID=1000 \
-e TZ=Asia/Taipei \
-p 9080:80 \
-v /home/monitor/smokeping/config:/config \
-v /home/monitor/smokeping/data:/data \
--restart unless-stopped \
linuxserver/smokeping
```

## fix config

<span style="font-family: tahoma, arial, helvetica, sans-serif;">路径 /home/monitor/smokeping/config/Targets</span>

```shell
#!/bin/bash
docker rm -f smokeping

docker run -d \
--name=smokeping \
-e PUID=1000 \
-e PGID=1000 \
-e TZ=Asia/Taipei \
-p 9080:80 \
-v /home/monitor/smokeping/config:/config \
-v /home/monitor/smokeping/data:/data \
--restart unless-stopped \
linuxserver/smokeping


```

<span style="font-family: tahoma, arial, helvetica, sans-serif;">page</span>

##### <span style="font-family: tahoma, arial, helvetica, sans-serif;">http://localhost:9080/smokeping/?target=InternetSites</span>

https://www.ssvip.net/971.html

# 【DNS】台灣地區常用 DNS IP 整合表

### 🇹🇼 台灣地區常用 DNS IP 整合表：

<div id="bkmrk-%E6%A5%AD%E8%80%85-%E4%B8%BB%E7%94%A8-dns-ip-%E5%82%99%E7%94%A8-%2F-%E5%8D%80%E5%9F%9F"><table data-end="1306" data-start="123"><thead data-end="220" data-start="123"><tr data-end="220" data-start="123"><th data-end="136" data-start="123">業者</th><th data-end="156" data-start="136">主用 DNS IP</th><th data-end="212" data-start="156">備用 / 區域性 DNS IP</th><th data-end="220" data-start="212">備註說明</th></tr></thead><tbody data-end="1306" data-start="334"><tr data-end="439" data-start="334"><td>**中華電信**</td><td>168.95.1.1</td><td>168.95.192.1</td><td>全台通用</td></tr><tr data-end="562" data-start="440"><td>**台灣大哥大**</td><td>203.73.24.1</td><td>203.73.25.1、61.31.233.1</td><td>固網與行動使用者常見 IP，會依區域調整</td></tr><tr data-end="735" data-start="563"><td>**遠傳電信 (SeedNet)**</td><td>139.175.1.1</td><td>139.175.55.244、8.8.8.8（Google DNS）、其他區域性如下：</td><td>官方資料來源 <a data-end="733" data-start="671" rel="noopener" target="_new">SeedNet</a></td></tr><tr data-end="850" data-start="736"><td>→ 北區</td><td>139.175.55.244</td><td>139.175.252.16</td><td>台北、桃園、新竹等</td></tr><tr data-end="965" data-start="851"><td>→ 中區</td><td>139.175.150.20</td><td>139.175.55.244</td><td>台中、彰化、南投等</td></tr><tr data-end="1080" data-start="966"><td>→ 南區</td><td>139.175.10.20</td><td>139.175.55.244</td><td>高雄、台南、嘉義等</td></tr><tr data-end="1192" data-start="1081"><td>**亞太電信**</td><td>多沿用中華電信</td><td> </td><td>建議手動指定 168.95.1.1</td></tr><tr data-end="1306" data-start="1193"><td>**台固媒體**</td><td>203.75.129.130</td><td>203.75.129.131</td><td>固定使用者常見 DNS</td></tr></tbody></table>

</div>---

### 🌐 國際公共 DNS（可自由選擇）：

<div id="bkmrk-%E6%9C%8D%E5%8B%99%E5%95%86-%E4%B8%BB%E7%94%A8-dns-ip-%E5%82%99%E7%94%A8-dns"><table data-end="1759" data-start="1338"><thead data-end="1401" data-start="1338"><tr data-end="1401" data-start="1338"><th data-end="1351" data-start="1338">服務商</th><th data-end="1367" data-start="1351">主用 DNS IP</th><th data-end="1385" data-start="1367">備用 DNS IP</th><th data-end="1401" data-start="1385">備註</th></tr></thead><tbody data-end="1759" data-start="1477"><tr data-end="1547" data-start="1477"><td>Google DNS</td><td>8.8.8.8</td><td>8.8.4.4</td><td>全球穩定快速</td></tr><tr data-end="1619" data-start="1548"><td>Cloudflare</td><td>1.1.1.1</td><td>1.0.0.1</td><td>快速且注重隱私</td></tr><tr data-end="1689" data-start="1620"><td>Quad9</td><td>9.9.9.9</td><td>149.112.112.112</td><td>防惡意網站 DNS</td></tr><tr data-end="1759" data-start="1690"><td>OpenDNS</td><td>208.67.222.222</td><td>208.67.220.220</td><td>可選擇家長過濾功能</td></tr></tbody></table>

</div>

# 【常見問題】通訊協定＆port 在呼叫上的差異

以下是針對你提到的三種不同 URL 呼叫方式：

```
1. https://a.b.com:3000
2. http://a.b.com:3000
3. http://a.b.com

```

---

## ✅ 一、三種呼叫差異總覽

<table id="bkmrk-%E5%91%BC%E5%8F%AB%E6%96%B9%E5%BC%8F-%E5%8D%94%E5%AE%9A-port-%E6%98%AF%E5%90%A6%E5%8A%A0%E5%AF%86-%E5%B0%8D-"><thead><tr><th>呼叫方式</th><th>協定</th><th>Port</th><th>是否加密</th><th>對 Nginx 的需求</th><th>常見用途</th></tr></thead><tbody><tr><td>`https://a.b.com:3000`</td><td>HTTPS</td><td>3000</td><td>✅ 是</td><td>`listen 3000 ssl;`</td><td>測試自訂 HTTPS port</td></tr><tr><td>`http://a.b.com:3000`</td><td>HTTP</td><td>3000</td><td>❌ 否</td><td>`listen 3000;`</td><td>自訂 HTTP port 開發</td></tr><tr><td>`http://a.b.com`</td><td>HTTP</td><td>80</td><td>❌ 否</td><td>`listen 80;`（預設）</td><td>一般公開網站</td></tr></tbody></table>

---

## ✅ 二、為什麼會有差異？

### 1. 協定不同（HTTP vs HTTPS）

- HTTP：明文傳輸，不需要 TLS 憑證。
- HTTPS：加密傳輸，需要 SSL 憑證，並透過 TLS 握手。

### 2. Port 不同（預設 vs 自訂）

- HTTP 預設是 80、HTTPS 預設是 443。
- 如果你指定其他 port（如 3000），伺服器端（如 Nginx）**必須額外開啟對應的 port 並配置好 listen 與憑證**。

---

## ✅ 三、Nginx 實際設定對應

### 1️⃣ 對應 `https://a.b.com:3000`

```nginx
server {
    listen 3000 ssl;
    server_name a.b.com;

    ssl_certificate     /etc/nginx/certs/a.b.com.crt;
    ssl_certificate_key /etc/nginx/certs/a.b.com.key;

    location / {
        proxy_pass http://localhost:4000;
    }
}

```

> 🔒 加密連線，必須設定 `ssl` 與有效憑證。

---

### 2️⃣ 對應 `http://a.b.com:3000`

```nginx
server {
    listen 3000;
    server_name a.b.com;

    location / {
        proxy_pass http://localhost:4000;
    }
}

```

> 🔓 明文傳輸，不需要 SSL 憑證。適用於測試環境或內網。

---

### 3️⃣ 對應 `http://a.b.com`

```nginx
server {
    listen 80;
    server_name a.b.com;

    location / {
        proxy_pass http://localhost:4000;
    }
}

```

> 🌐 最常見的網站 HTTP 配置。不需帶 port。

---

## ✅ 四、連線行為對照表

<table id="bkmrk-%E4%BD%BF%E7%94%A8%E8%80%85%E8%BC%B8%E5%85%A5%E7%B6%B2%E5%9D%80-%E7%80%8F%E8%A6%BD%E5%99%A8%E6%88%96-curl-%E7%99%BC%E5%87%BA"><thead><tr><th>使用者輸入網址</th><th>瀏覽器或 curl 發出的請求會去到哪裡？</th></tr></thead><tbody><tr><td>`https://a.b.com:3000`</td><td>`a.b.com` 的 **port 3000**（用 TLS）</td></tr><tr><td>`http://a.b.com:3000`</td><td>`a.b.com` 的 **port 3000**（無加密）</td></tr><tr><td>`http://a.b.com`</td><td>`a.b.com` 的 **port 80**</td></tr></tbody></table>

---

## ✅ 五、常見錯誤與排查

<table id="bkmrk-%E5%95%8F%E9%A1%8C-%E5%8E%9F%E5%9B%A0-%E8%A7%A3%E6%B3%95-curl-https%3A"><thead><tr><th>問題</th><th>原因</th><th>解法</th></tr></thead><tbody><tr><td>`curl https://a.b.com:3000` 報錯 TLS</td><td>你設定了 HTTPS，但 Nginx 未加上 `ssl` 或憑證有誤</td><td>加上 `ssl` 並設定憑證</td></tr><tr><td>只能用 `http://a.b.com`，其他都失敗</td><td>Nginx 只開 port 80，沒開 3000</td><td>增加對應 `listen 3000` 的設定</td></tr><tr><td>呼叫 `http://a.b.com:3000` 被拒絕</td><td>Server 未開啟該 port</td><td>檢查防火牆、Nginx、Docker 映射是否有允許 3000</td></tr></tbody></table>

---

## ✅ 六、總結：選擇哪一種？

<table id="bkmrk-%E4%BD%BF%E7%94%A8%E5%A0%B4%E6%99%AF-%E5%BB%BA%E8%AD%B0%E5%91%BC%E5%8F%AB%E6%96%B9%E5%BC%8F-%E5%85%A7%E9%83%A8%E9%96%8B%E7%99%BC%E6%B8%AC%E8%A9%A6-h"><thead><tr><th>使用場景</th><th>建議呼叫方式</th></tr></thead><tbody><tr><td>內部開發測試</td><td>`http://a.b.com:3000` 或 `https://a.b.com:3000`</td></tr><tr><td>正式站公開服務</td><td>`https://a.b.com`（預設 port 443）</td></tr><tr><td>Nginx 反向代理串接後端</td><td>視你要轉發的是 HTTP or HTTPS 及 port 而定</td></tr></tbody></table>

---

如你需要一份完整支援三種 port 的 `nginx.conf` 以及搭配 Express 的後端範例，我可以幫你整合好。需要嗎？

# 【常見問題】https 使用domain 與 ip 差異

好的，以下補上：

---

## ✅ 比較 `https://a.b.com` 與 `https://168.23.1.1` 的差異

這兩者**協定一樣**（都是 HTTPS），但**主機名稱（Host）差異會影響 SSL 憑證驗證、DNS 解析、與 Nginx 的設定行為**。

---

##  差異對照表

<table id="bkmrk-%E9%A0%85%E7%9B%AE-https%3A%2F%2Fa.b.com-h"><thead><tr><th>項目</th><th>`https://a.b.com`</th><th>`https://168.23.1.1`</th></tr></thead><tbody><tr><td>**協定**</td><td>HTTPS（加密）</td><td>HTTPS（加密）</td></tr><tr><td>**主機名稱**</td><td>網域名稱（FQDN）</td><td>IP 位址</td></tr><tr><td>**DNS 解析**</td><td>會經過 DNS 解析 → IP</td><td>不經 DNS</td></tr><tr><td>**SSL 憑證驗證**</td><td>通常需要**憑證中的 CN/SAN 包含 a.b.com**</td><td>需要**憑證中包含 IP 位址**（不常見）</td></tr><tr><td>**Nginx `server_name`** 配置</td><td>`server_name a.b.com;`</td><td>`server_name 168.23.1.1;`（不建議）或 `_`</td></tr><tr><td>**常見用法**</td><td>公網、正式站、內部網域皆可</td><td>測試、開發、內網 IP 使用</td></tr><tr><td>**風險與限制**</td><td>SSL 憑證驗證容易通過</td><td>一般憑證不支援 IP → 會出現 TLS 錯誤</td></tr><tr><td>**信任憑證**</td><td>可用 Let's Encrypt、商業憑證</td><td>除非特別申請 IP 憑證，否則會有警告</td></tr></tbody></table>

---

##  SSL 憑證驗證差異舉例

### `https://a.b.com`

- 若你使用 Let's Encrypt 為 `a.b.com` 建立憑證：  
    ✅ `curl https://a.b.com` ➜ 成功（CN/SAN 包含 `a.b.com`）

### `https://168.23.1.1`

- 若你拿 `a.b.com` 憑證套用到 IP 上：  
    ❌ `curl https://168.23.1.1` ➜ 失敗，出現 `SSL: certificate subject name does not match`

###  解法（不建議用於正式環境）：

- 為 IP 簽發 SSL 憑證（需自簽或企業憑證）
- 用 `curl -k` 忽略憑證錯誤

```bash
curl -k https://168.23.1.1  # 跳過憑證驗證（不安全）

```

---

## ✅ Nginx 設定差異

### `a.b.com` 對應設定：

```nginx
server {
    listen 443 ssl;
    server_name a.b.com;

    ssl_certificate     /etc/nginx/certs/a.b.com.crt;
    ssl_certificate_key /etc/nginx/certs/a.b.com.key;

    location / {
        proxy_pass http://localhost:3000;
    }
}

```

---

### IP 直接連線時，通常會用：

```nginx
server {
    listen 443 ssl;
    server_name _;  # 匹配所有 hostname（或不設定）

    ssl_certificate     /etc/nginx/certs/selfsigned.crt;
    ssl_certificate_key /etc/nginx/certs/selfsigned.key;

    location / {
        proxy_pass http://localhost:3000;
    }
}

```

> 🧨 若直接用 IP，也許不會觸發 `server_name` 對應，取決於 Nginx 設定順序與 SNI。

---

## ✅ 小結

<table id="bkmrk-url-%E9%81%A9%E7%94%A8%E6%99%82%E6%A9%9F-%E6%86%91%E8%AD%89%E9%9C%80%E6%B1%82-%E9%A2%A8%E9%9A%AA-htt"><thead><tr><th>URL</th><th>適用時機</th><th>憑證需求</th><th>風險</th></tr></thead><tbody><tr><td>`https://a.b.com`</td><td>公網、正常使用</td><td>CN/SAN 需含 `a.b.com`</td><td>正常通過憑證驗證</td></tr><tr><td>`https://168.23.1.1`</td><td>內部測試或特例</td><td>憑證需含 IP 或自簽</td><td>容易驗證失敗、跳警告</td></tr></tbody></table>

---

##  

## ✅Curl Domain 與 ip 的 差異

這是一個非常關鍵的細節問題，涉及到 **HTTPS、Host Header、SNI（Server Name Indication）**，我們來完整拆解：

---

## 你提供的兩個請求：

```bash
curl https://a.b.com

```

```bash
curl https://168.23.1.1 -H 'Host: a.b.com'

```

看起來類似，實際上在底層**行為非常不同**：

---

##  核心差異一覽

<table id="bkmrk-%E9%A0%85%E7%9B%AE-curl-https%3A%2F%2Fa.b."><thead><tr><th>項目</th><th>`curl https://a.b.com`</th><th>`curl https://168.23.1.1 -H 'Host: a.b.com'`</th></tr></thead><tbody><tr><td>**連線 IP**</td><td>DNS 解析 `a.b.com` → IP</td><td>直接指定 IP `168.23.1.1`</td></tr><tr><td>**TLS SNI 傳送內容**</td><td>`a.b.com`（🔑 用來選擇憑證）</td><td>`168.23.1.1`（🚨 TLS 層不包含 `a.b.com`）</td></tr><tr><td>**Host Header（HTTP 層）**</td><td>`Host: a.b.com`（由 curl 自動加）</td><td>`Host: a.b.com`（你手動加）</td></tr><tr><td>**SSL 憑證是否驗證成功？**</td><td>✅ 通常會成功</td><td>❌ 若憑證是發給 `a.b.com` 則會 mismatch</td></tr><tr><td>**Nginx 是否能進到正確的 server**</td><td>✅ 通常會進到 `server_name a.b.com`</td><td>❌ 取決於 SNI，可能進入 default server</td></tr></tbody></table>

---

##  為何 TLS 憑證驗證會失敗？

###  SNI 是什麼？

- TLS 層在握手階段會送出 `Server Name`（來自 `curl` 的 `--resolve` 或 URL）。
- 用來讓伺服器知道要回哪一張憑證。

### 在兩種情況下比較：

#### ✅ `curl https://a.b.com`：

- DNS → 168.23.1.1
- SNI: `a.b.com`
- Host header: `a.b.com`
- Nginx 匹配成功，憑證也是給 `a.b.com`，一切正常

#### ❌ `curl https://168.23.1.1 -H 'Host: a.b.com'`：

- SNI: `168.23.1.1`（預設值，非 a.b.com）
- Host header: `a.b.com`（只有 HTTP 層有用）
- 若 Nginx 用 `server_name a.b.com` 並綁定憑證，那會 **因為 SNI 不符無法選中正確的憑證 → TLS 握手失敗**

---

## Nginx 對這兩種請求的反應

```nginx
server {
    listen 443 ssl;
    server_name a.b.com;

    ssl_certificate     /etc/nginx/certs/a.b.com.crt;
    ssl_certificate_key /etc/nginx/certs/a.b.com.key;

    location / {
        return 200 "hello a.b.com\n";
    }
}

server {
    listen 443 ssl default_server;
    server_name _;

    ssl_certificate     /etc/nginx/certs/default.crt;
    ssl_certificate_key /etc/nginx/certs/default.key;

    location / {
        return 200 "default server\n";
    }
}

```

### 測試結果：

<table id="bkmrk-%E6%B8%AC%E8%A9%A6%E6%8C%87%E4%BB%A4-%E4%BC%BA%E6%9C%8D%E5%99%A8-block-%E5%91%BD%E4%B8%AD-%E5%9B%9E%E5%82%B3"><thead><tr><th>測試指令</th><th>伺服器 block 命中</th><th>回傳內容</th><th>是否 SSL 通過</th></tr></thead><tbody><tr><td>`curl https://a.b.com`</td><td>`server_name a.b.com`</td><td>hello a.b.com</td><td>✅ 是</td></tr><tr><td>`curl https://168.23.1.1 -H 'Host: a.b.com'`</td><td>`server_name _`（default）</td><td>default server</td><td>⚠️ 通常憑證不符（或需 `-k`）</td></tr></tbody></table>

---

## ✅ 解法：讓 IP 請求也能通過 SSL

### 方法一：用自簽或專門支援 IP 的憑證

但大多數公開 CA（如 Let's Encrypt）**不發給 IP 憑證**。

### 方法二：用 curl 忽略 TLS 驗證

```bash
curl -k https://168.23.1.1 -H 'Host: a.b.com'

```

### 方法三：讓 curl 傳送正確的 SNI（推薦）

```bash
curl --resolve a.b.com:443:168.23.1.1 https://a.b.com

```

- 這會讓 curl：
    
    
    - SNI 傳 `a.b.com`
    - Host header 是 `a.b.com`
    - 實際連到 IP `168.23.1.1`
- ✅ 完整模擬瀏覽器行為

---

## ✅ 總結

<table id="bkmrk-%E6%AF%94%E8%BC%83%E9%A0%85%E7%9B%AE-curl-https%3A%2F%2Fa."><thead><tr><th>比較項目</th><th>`curl https://a.b.com`</th><th>`curl https://168.23.1.1 -H 'Host: a.b.com'`</th></tr></thead><tbody><tr><td>DNS 解析</td><td>✅ 有</td><td>❌ 無</td></tr><tr><td>TLS SNI</td><td>`a.b.com`</td><td>`168.23.1.1`</td></tr><tr><td>Host Header</td><td>`a.b.com`</td><td>`a.b.com`</td></tr><tr><td>SSL 憑證驗證</td><td>✅ 通常成功</td><td>❌ 會失敗（除非用 `-k`）</td></tr><tr><td>建議用法</td><td>✅ 正常連線方式</td><td>⚠️ 用於測試或模擬</td></tr></tbody></table>

---

如果你需要模擬真實請求又繞過 DNS，可使用：

```bash
curl --resolve a.b.com:443:168.23.1.1 https://a.b.com

```

這是最穩定且最貼近瀏覽器 SNI 行為的方式。

# 【PowerDNS】

Lua records

[https://doc.powerdns.com/authoritative/lua-records/index.html](https://doc.powerdns.com/authoritative/lua-records/index.html)

```
-- 返回请求方 IP
who:toString()

-- 根据请求方 IP 不同返回不同地址
view({
  {{'10.10.0.0/16'}, {'10.10.1.1'}}
  {{'0.0.0.0/0'}, {'192.0.2.1'}}
})

-- 返回存活的 IP
-- 异步检测 - 如果都不存活则都返回
ifportup(443, {'192.0.2.1', '192.0.2.2'})
-- 使用 URL 检测存活 - stringmatch 为可选的内容匹配条件
ifurlup("example.com/status", { {"192.0.2.20", "203.0.113.4"}, {"203.0.113.2"} },{stringmatch='ok'})

-- 基于请求方的 HASH(bestwho) 进行返回
pickwhashed({{10,'192.168.0.1'},{100,'192.168.0.2'}})
-- 随机返回 - 有带权重版
pickrandom({{'192.168.0.1'},{'192.168.0.2'}})
pickwrandom({{10,'192.168.0.1'},{100,'192.168.0.2'}})
```

### **`pdnsutil`**

下面是 **`pdnsutil`**（PowerDNS 的指令工具，用於 DNS 區域與 DNSSEC 管理）的一份常用語法與操作範例教學。你可以把它當作 cheat-sheet 或操作對照表。  
（若你的 PowerDNS 版本略有不同或後續改版，部分語法可能略有出入，建議搭配 `man pdnsutil` 或官方文檔確認） ([doc.powerdns.com](https://doc.powerdns.com/authoritative/manpages/pdnsutil.1.html?utm_source=chatgpt.com "pdnsutil — PowerDNS Authoritative Server documentation"))

---

## 基本語法結構與通用選項

```
pdnsutil [OPTIONS] COMMAND [ARGUMENTS...]

```

常見的選項：

<table id="bkmrk-%E9%81%B8%E9%A0%85-%E8%AA%AA%E6%98%8E--h%2C---help-%E9%A1%AF%E7%A4%BA%E5%8D%94"><thead><tr><th>選項</th><th>說明</th></tr></thead><tbody><tr><td>`-h, --help`</td><td>顯示協助訊息</td></tr><tr><td>`-v, --verbose`</td><td>輸出較詳細訊息</td></tr><tr><td>`--force`</td><td>強制執行（覆寫、跳過某些檢查等）</td></tr><tr><td>`--config-name <NAME>`</td><td>指定虛擬設定名稱</td></tr><tr><td>`--config-dir <DIR>`</td><td>指定 pdns.conf 所在目錄（預設 `/etc/powerdns`）</td></tr></tbody></table>

---

## 常見區域 (zone) 管理指令

以下指令幾乎涵蓋新增、查看、編輯、刪除、檢查等需求。

<table id="bkmrk-%E5%8A%9F%E8%83%BD-%E6%8C%87%E4%BB%A4%E7%AF%84%E4%BE%8B-%E8%AA%AA%E6%98%8E-%2F-%E5%82%99%E8%A8%BB-%E5%BB%BA%E7%AB%8B%E5%8D%80%E5%9F%9F"><thead><tr><th>功能</th><th>指令範例</th><th>說明 / 備註</th></tr></thead><tbody><tr><td>建立區域</td><td>`pdnsutil create-zone example.com`</td><td>建立一個空的 zone `example.com`，內部會自動加入一条 NS 記錄 ([blog.powerdns.com](https://blog.powerdns.com/2016/02/02/powerdns-authoritative-the-new-old-way-to-manage-domains?utm_source=chatgpt.com "PowerDNS Authoritative: the new old way to manage domains"))</td></tr><tr><td>刪除區域</td><td>`pdnsutil delete-zone example.com`</td><td>將該 zone 從後端資料庫中移除</td></tr><tr><td>顯示區域內容</td><td>`pdnsutil show-zone example.com`</td><td>列出 zone 的所有 resource record 和 meta 資訊 ([blog.powerdns.com](https://blog.powerdns.com/2016/02/02/powerdns-authoritative-the-new-old-way-to-manage-domains?utm_source=chatgpt.com "PowerDNS Authoritative: the new old way to manage domains"))</td></tr><tr><td>列出區域</td><td>`pdnsutil list-zone example.com`</td><td>列出該區域的所有資料記錄 ([blog.powerdns.com](https://blog.powerdns.com/2016/02/02/powerdns-authoritative-the-new-old-way-to-manage-domains?utm_source=chatgpt.com "PowerDNS Authoritative: the new old way to manage domains"))</td></tr><tr><td>列出所有區域</td><td>`pdnsutil list-all-zones`</td><td>顯示所有已註冊的 zone 名稱 ([Debian Manpages](https://manpages.debian.org/testing/pdns-server/pdnsutil.1.en.html?utm_source=chatgpt.com "pdnsutil(1) — pdns-server — Debian testing"))</td></tr><tr><td>檢查單個區域</td><td>`pdnsutil check-zone example.com`</td><td>檢查該 zone 是否有錯誤（語法、衝突等） ([cPanel Support](https://support.cpanel.net/hc/en-us/articles/360058804574-How-to-use-PowerDNS-tools-to-find-zone-errors?utm_source=chatgpt.com "How to use PowerDNS tools to find zone errors – cPanel"))</td></tr><tr><td>檢查所有區域</td><td>`pdnsutil check-all-zones`</td><td>對所有 zones 執行檢查 ([CloudWizard.nl](https://www.cloudwizard.nl/powerdns-part-3-create-zone-ubuntu/?utm_source=chatgpt.com "PowerDNS Part 3 - Create zone (Ubuntu) - CloudWizard.nl"))</td></tr><tr><td>編輯區域（交互式）</td><td>`pdnsutil edit-zone example.com`</td><td>進入文字編輯介面，可以手動修改 zone 的所有記錄（系統會做基本檢查） ([blog.powerdns.com](https://blog.powerdns.com/2016/02/02/powerdns-authoritative-the-new-old-way-to-manage-domains?utm_source=chatgpt.com "PowerDNS Authoritative: the new old way to manage domains"))</td></tr><tr><td>增加記錄</td><td>`pdnsutil add-record example.com sub A 3600 1.2.3.4`</td><td>在 `example.com` 區域新增一條 A 紀錄： `sub.example.com` TTL 3600 指向 1.2.3.4 ([blog.powerdns.com](https://blog.powerdns.com/2016/02/02/powerdns-authoritative-the-new-old-way-to-manage-domains?utm_source=chatgpt.com "PowerDNS Authoritative: the new old way to manage domains"))</td></tr><tr><td>更新（替換）整組記錄</td><td>`pdnsutil replace-rrset example.com sub A 3600 5.6.7.8`</td><td>將 sub 的 A 類型記錄整組替換為新的值（如果有多條） ([blog.powerdns.com](https://blog.powerdns.com/2016/02/02/powerdns-authoritative-the-new-old-way-to-manage-domains?utm_source=chatgpt.com "PowerDNS Authoritative: the new old way to manage domains"))</td></tr><tr><td>刪除一組記錄</td><td>`pdnsutil delete-rrset example.com sub A`</td><td>刪除 sub 的所有 A 類型記錄（整個 RRset） ([HackMD](https://hackmd.io/%40haquenafeem/Sy20t2W6d?utm_source=chatgpt.com "PowerDNS R&D"))</td></tr><tr><td>增加序號</td><td>`pdnsutil increase-serial example.com`</td><td>手動把 SOA 的 Serial 加一（通常在變更後需告知 slave） ([CloudWizard.nl](https://www.cloudwizard.nl/powerdns-part-3-create-zone-ubuntu/?utm_source=chatgpt.com "PowerDNS Part 3 - Create zone (Ubuntu) - CloudWizard.nl"))</td></tr><tr><td>設定 zone 類型</td><td>`pdnsutil set-kind example.com MASTER`</td><td>設定該 zone 為主(zone master)、從(zone slave)或其他類型 ([CloudWizard.nl](https://www.cloudwizard.nl/powerdns-part-3-create-zone-ubuntu/?utm_source=chatgpt.com "PowerDNS Part 3 - Create zone (Ubuntu) - CloudWizard.nl"))</td></tr><tr><td>設定 meta (zone metadata)</td><td>`pdnsutil set-meta example.com ALLOW-AXFR-FROM AUTO-NS`</td><td>為該 zone 加上 metadata，例如允許哪些 IP 做 AXFR 等 ([blog.powerdns.com](https://blog.powerdns.com/2016/02/02/powerdns-authoritative-the-new-old-way-to-manage-domains?utm_source=chatgpt.com "PowerDNS Authoritative: the new old way to manage domains"))</td></tr></tbody></table>

---

## DNSSEC / 金鑰 (key) 管理指令

如果你要為 zone 啟用 DNSSEC、管理金鑰或做 key rollover 等，這些指令就會派上用場。 ([doc.powerdns.com](https://doc.powerdns.com/authoritative/dnssec/pdnsutil.html?utm_source=chatgpt.com "pdnsutil and DNSSEC - PowerDNS Documentation"))

<table id="bkmrk-%E5%8A%9F%E8%83%BD-%E6%8C%87%E4%BB%A4%E7%AF%84%E4%BE%8B-%E8%AA%AA%E6%98%8E-%2F-%E5%82%99%E8%A8%BB-%E5%95%9F%E7%94%A8-d"><thead><tr><th>功能</th><th>指令範例</th><th>說明 / 備註</th></tr></thead><tbody><tr><td>啟用 DNSSEC（簽名 zone）</td><td>`pdnsutil secure-zone example.com`</td><td>為該 zone 建立金鑰並簽名（預設配置） ([doc.powerdns.com](https://doc.powerdns.com/authoritative/dnssec/pdnsutil.html?utm_source=chatgpt.com "pdnsutil and DNSSEC - PowerDNS Documentation"))</td></tr><tr><td>列出該 zone 的金鑰</td><td>`pdnsutil list-keys example.com`</td><td>顯示該 zone 所有的 KSK / ZSK 金鑰資訊 ([SIDN - Het bedrijf achter .nl](https://www.sidn.nl/en/modern-internet-standards/dnssec-on-the-powerdns-authoritative-server?utm_source=chatgpt.com "DNSSEC on the PowerDNS Authoritative Server - SIDN"))</td></tr><tr><td>新增金鑰</td><td>`pdnsutil add-zone-key example.com ZSK active 2048 rsasha256`</td><td>為例：在 zone `example.com` 新增一組 ZSK 金鑰，256 RSA, 2048 位元長度。可調整參數。 ([Debian Manpages](https://manpages.debian.org/testing/pdns-server/pdnsutil.1.en.html?utm_source=chatgpt.com "pdnsutil(1) — pdns-server — Debian testing"))</td></tr><tr><td>啟用金鑰</td><td>`pdnsutil activate-zone-key example.com 12345`</td><td>啟用某一個金鑰（以其 key-id 為識別） ([Debian Manpages](https://manpages.debian.org/testing/pdns-server/pdnsutil.1.en.html?utm_source=chatgpt.com "pdnsutil(1) — pdns-server — Debian testing"))</td></tr><tr><td>停用金鑰</td><td>`pdnsutil deactivate-zone-key example.com 12345`</td><td>停用該金鑰（使其不再用於簽名） ([Debian Manpages](https://manpages.debian.org/testing/pdns-server/pdnsutil.1.en.html?utm_source=chatgpt.com "pdnsutil(1) — pdns-server — Debian testing"))</td></tr><tr><td>匯出公鑰與 DS 記錄</td><td>`pdnsutil export-zone-ds example.com`</td><td>匯出該 zone 所有 KSK 的 DS 記錄（可提交給上層域名註冊商） ([SIDN - Het bedrijf achter .nl](https://www.sidn.nl/en/modern-internet-standards/dnssec-on-the-powerdns-authoritative-server?utm_source=chatgpt.com "DNSSEC on the PowerDNS Authoritative Server - SIDN"))</td></tr><tr><td>匯出金鑰</td><td>`pdnsutil export-zone-key example.com 12345`</td><td>匯出該 zone 指定金鑰（含私鑰）到檔案或標準輸出 ([SIDN - Het bedrijf achter .nl](https://www.sidn.nl/en/modern-internet-standards/dnssec-on-the-powerdns-authoritative-server?utm_source=chatgpt.com "DNSSEC on the PowerDNS Authoritative Server - SIDN"))</td></tr><tr><td>移除金鑰</td><td>`pdnsutil remove-zone-key example.com 12345`</td><td>將指定金鑰從 zone 中移除（不再保留） ([SIDN - Het bedrijf achter .nl](https://www.sidn.nl/en/modern-internet-standards/dnssec-on-the-powerdns-authoritative-server?utm_source=chatgpt.com "DNSSEC on the PowerDNS Authoritative Server - SIDN"))</td></tr><tr><td>停用 DNSSEC（取消簽名）</td><td>`pdnsutil disable-dnssec example.com`</td><td>將 zone 還原為不使用 DNSSEC 狀態 ([SIDN - Het bedrijf achter .nl](https://www.sidn.nl/en/modern-internet-standards/dnssec-on-the-powerdns-authoritative-server?utm_source=chatgpt.com "DNSSEC on the PowerDNS Authoritative Server - SIDN"))</td></tr><tr><td>修正 (rectify) zone 資料庫</td><td>`pdnsutil rectify-zone example.com`</td><td>重建 zone 在資料庫中的 internal 欄位（如 auth / ordername） ([SIDN - Het bedrijf achter .nl](https://www.sidn.nl/en/modern-internet-standards/dnssec-on-the-powerdns-authoritative-server?utm_source=chatgpt.com "DNSSEC on the PowerDNS Authoritative Server - SIDN"))</td></tr><tr><td>修正所有 zone</td><td>`pdnsutil rectify-all-zones`</td><td>對所有已存在的 zone 執行 rectify 操作 ([SIDN - Het bedrijf achter .nl](https://www.sidn.nl/en/modern-internet-standards/dnssec-on-the-powerdns-authoritative-server?utm_source=chatgpt.com "DNSSEC on the PowerDNS Authoritative Server - SIDN"))</td></tr></tbody></table>

---

## 一些操作流程範例

以下是比較常見的操作流程，依步驟示範怎麼做：

### 範例：建立一個新的 zone 並設定記錄 + 啟用 DNSSEC

假設 domain 為 `example.com`：

```bash
pdnsutil create-zone example.com
pdnsutil set-kind example.com MASTER
pdnsutil add-record example.com @ NS ns1.example.com.
pdnsutil add-record example.com @ NS ns2.example.com.
pdnsutil add-record example.com ns1 A 1.2.3.4
pdnsutil add-record example.com ns2 A 5.6.7.8
pdnsutil replace-rrset example.com . SOA 'ns1.example.com. hostmaster.example.com. 1 10800 3600 604800 3600'
pdnsutil secure-zone example.com
pdnsutil increase-serial example.com
pdns_control notify example.com

```

這樣做完後，你可以用 `pdnsutil show-zone example.com` 檢查結果。 ([CloudWizard.nl](https://www.cloudwizard.nl/powerdns-part-3-create-zone-ubuntu/?utm_source=chatgpt.com "PowerDNS Part 3 - Create zone (Ubuntu) - CloudWizard.nl"))

### 範例：修改已有記錄

假設要把 `www.example.com` 的 A 紀錄從 `1.2.3.4` 換成 `5.5.5.5`：

```bash
pdnsutil replace-rrset example.com www A 3600 5.5.5.5
pdnsutil increase-serial example.com
pdns_control notify example.com

```

或若要直接刪除 `www` 的 A 紀錄：

```bash
pdnsutil delete-rrset example.com www A
pdnsutil increase-serial example.com
pdns_control notify example.com

```

最後別忘了通知 slave 或其他 server 更新：`pdns_control notify example.com`。

---

## 注意事項與建議

1. **後端限制**  
    某些後端 (特別是 BIND 後端) 可能不支援修改操作（只能透過 zone transfer）或不支援 `pdnsutil` 操作。要確認你使用的後端是否允許這些操作。 ([Reddit](https://www.reddit.com/r/dns/comments/ju57uf/powerdns_cannot_add_records_with_pdnsutil/?utm_source=chatgpt.com "PowerDNS: cannot add records with pdnsutil : r/dns - Reddit"))
2. **版本差異**  
    不同 PowerDNS 版本對於 DNSSEC 的預設邏輯、支援的演算法、金鑰長度限制等可能不同。建議查看你系統上 `man pdnsutil` 或官方版本對照表。 ([Debian Manpages](https://manpages.debian.org/testing/pdns-server/pdnsutil.1.en.html?utm_source=chatgpt.com "pdnsutil(1) — pdns-server — Debian testing"))
3. **序號 (Serial) 管理**  
    在變更 zone 後，務必更新 SOA 的 serial（可用 `increase-serial` 或手動編輯 SOA）並通知 slave。否則 slave 可能不會接收到變更。
4. **測試 / 錯誤檢查**  
    使用 `check-zone` / `check-all-zones` 來檢查語法或邏輯錯誤，以免上線後 DNS 無法解析。 ([cPanel Support](https://support.cpanel.net/hc/en-us/articles/360058804574-How-to-use-PowerDNS-tools-to-find-zone-errors?utm_source=chatgpt.com "How to use PowerDNS tools to find zone errors – cPanel"))
5. **備份與金鑰匯出**  
    在做 DNSSEC 金鑰 rollover 或改動前，建議匯出金鑰備份（`export-zone-key`）及對 DS 記錄操作要小心，避免中斷解析鏈。

---

## 🧩 一、PowerDNS 查詢流程（簡化版）

當你執行：

```bash
dig app-wekan.momoshop.com.tw

```

時，PDNS 的查詢處理順序如下：

1. **找 zone（domains 表）**
    
    
    - 從 `domains.name` 開始，找最匹配的 zone（例如 `momoshop.com.tw`）。
2. **在 records 表中找符合的 name**
    
    
    - 順序為：
        
        
        - 精確比對 `name='app-wekan.momoshop.com.tw'`
        - 如果找不到，則依序比對：
            
            
            - `*.momoshop.com.tw`
            - `*.com.tw`
            - `*.tw`
    - 每一層可能都會回傳多筆記錄。
3. **根據 type 過濾**
    
    
    - 只會選出符合查詢類型（例如 A）或可解譯的 LUA record。
4. **若有 LUA 記錄，執行 Lua 腳本**
    
    
    - 若多筆 LUA record，PDNS 會依「**資料庫記錄順序（id 由小到大）**」逐筆執行；
    - 直到第一筆成功產生合法的回應為止。
5. **若多筆普通記錄（A, CNAME）**
    
    
    - 會**全部回傳**；
    - 若 TTL 不同，取最小 TTL 為最終 TTL。
6. **若無任何匹配**
    
    
    - 回覆 `NXDOMAIN` 或 `SERVFAIL`（視 zone 與設定而定）。

---

## 🧠 二、LUA Record 的詳細執行順序

當 PDNS 找到多筆 `type='LUA'` 的記錄時：

<table id="bkmrk-%E9%A0%86%E5%BA%8F-%E8%A1%8C%E7%82%BA-1%EF%B8%8F%E2%83%A3-%E6%8C%89%E7%85%A7-id-%E7%94%B1%E5%B0%8F%E5%88%B0%E5%A4%A7"><thead><tr><th>順序</th><th>行為</th></tr></thead><tbody><tr><td>1️⃣</td><td>按照 `id` 由小到大排序</td></tr><tr><td>2️⃣</td><td>依序執行每筆 LUA 程式</td></tr><tr><td>3️⃣</td><td>第一個「有成功回傳記錄」的 LUA 結果會被採用</td></tr><tr><td>4️⃣</td><td>若 LUA 執行失敗（語法錯誤或回傳空值） → 嘗試下一筆</td></tr><tr><td>5️⃣</td><td>全部失敗 → 回 `SERVFAIL`</td></tr></tbody></table>

### ✅ 範例：

假設你有：

```sql
id | name               | type | content
---+--------------------+------+------------------------------------------
 1 | *.momoshop.com.tw  | LUA  | return getdns("10.2.1.11", qname, qtype)
 2 | *.momoshop.com.tw  | LUA  | return getdns("8.8.8.8", qname, qtype)

```

查詢 `app.momoshop.com.tw` 時：

- PDNS 會先執行第 1 筆（id=1），  
    如果 10.2.1.11 無法回應或錯誤 → 自動執行第 2 筆（id=2）。
- 若第 1 筆成功，第二筆不會被執行。

👉 因此你可以用多筆 LUA record 來實作「多層 fallback」。

---

## ⚙️ 三、普通記錄（非 LUA）的多筆處理邏輯

若同一個 name 有多筆非 LUA 記錄（例如多個 A 記錄）：

<table id="bkmrk-%E8%A8%98%E9%8C%84-ttl-ip-app.momosh"><thead><tr><th>記錄</th><th>TTL</th><th>IP</th></tr></thead><tbody><tr><td>app.momoshop.com.tw</td><td>60</td><td>10.2.1.11</td></tr><tr><td>app.momoshop.com.tw</td><td>120</td><td>10.2.1.12</td></tr></tbody></table>

PDNS 會：

- 同時回傳兩筆；
- 最終 TTL = min(60, 120) = 60。

回覆結果：

```
app.momoshop.com.tw. 60 IN A 10.2.1.11
app.momoshop.com.tw. 60 IN A 10.2.1.12

```

---

## 🔍 四、查詢過程中，實際的比對順序（Wildcard / LUA 混合時）

PowerDNS 在有通配符與 LUA 混用的情況下，查詢順序如下：

<table id="bkmrk-%E5%84%AA%E5%85%88%E9%A0%86%E5%BA%8F-%E6%AF%94%E5%B0%8D%E5%9E%8B%E6%85%8B-%E6%98%AF%E5%90%A6%E5%9F%B7%E8%A1%8C-lua-1"><thead><tr><th>優先順序</th><th>比對型態</th><th>是否執行 LUA</th></tr></thead><tbody><tr><td>1️⃣</td><td>精確比對（如 `app.momoshop.com.tw`）</td><td>是（若 type=LUA）</td></tr><tr><td>2️⃣</td><td>wildcard 比對（如 `*.momoshop.com.tw`）</td><td>是（若 type=LUA）</td></tr><tr><td>3️⃣</td><td>更高層 wildcard（如 `*.com.tw`）</td><td>是（若 type=LUA）</td></tr><tr><td>4️⃣</td><td>zone SOA / NS</td><td>否</td></tr><tr><td>5️⃣</td><td>沒有符合 → `NXDOMAIN` 或 `SERVFAIL`</td><td> </td></tr></tbody></table>

---

## 🧩 五、如何查看 PowerDNS 真實回應順序

你可以用：

```bash
pdnsutil list-zone momoshop.com.tw

```

觀察記錄順序。  
PowerDNS 的執行順序就是按照這個清單的排列（id遞增）。

若你要控制順序，可以直接修改 id 或刪除後重新新增。

---

## 🧪 六、測試方式建議

建立多筆 LUA fallback：

```sql
INSERT INTO records (domain_id, name, type, content, ttl)
VALUES
  (3, '*.momoshop.com.tw', 'LUA', 'return getdns("10.2.1.11", qname, qtype)', 60),
  (3, '*.momoshop.com.tw', 'LUA', 'return getdns("8.8.8.8", qname, qtype)', 60);

```

測試：

```bash
journalctl -u pdns -f &
dig @127.0.0.1 -p 5501 app.momoshop.com.tw

```

你會看到：

```
Executing LUA record id=1: getdns(10.2.1.11)
Failed: timeout
Executing LUA record id=2: getdns(8.8.8.8)
Success → returning result

```

---

## 🔚 結論摘要

<table id="bkmrk-%E9%A1%9E%E5%9E%8B-%E8%A1%8C%E7%82%BA-%E5%9B%9E%E6%87%89%E6%96%B9%E5%BC%8F-%E5%A4%9A%E7%AD%86-a-%E8%A8%98%E9%8C%84-%E5%85%A8"><thead><tr><th>類型</th><th>行為</th><th>回應方式</th></tr></thead><tbody><tr><td>多筆 A 記錄</td><td>全部回傳</td><td>TTL 取最小</td></tr><tr><td>多筆 LUA 記錄</td><td>依 ID 順序執行</td><td>第一筆成功即返回</td></tr><tr><td>通配符 vs 精確匹配</td><td>精確優先於通配符</td><td> </td></tr><tr><td>LUA 與非 LUA 混用</td><td>LUA 先執行</td><td>若 LUA 無結果再回傳靜態記錄</td></tr></tbody></table>

---

是否要我幫你畫一張「PowerDNS 回應決策順序」的流程圖（含 fallback 流程）？  
會清楚標出「多筆 LUA record 執行順序 → fallback 判斷 → 最終回應」。

# 【PowerDNS】Lua Records

這是一份 PowerDNS 官方文件的完整中文翻譯。  
它說明了 **LUA Record（Lua 動態記錄）** 的用途、語法、應用範例、進階用法與安全注意事項。  
我保留了原文段落結構與技術名詞，方便你比對。

---

# 🧩 PowerDNS — Lua Records 中文說明文件

## 一、概述

為了提供動態行為（例如全球負載平衡 Global Server Load Balancing），  
PowerDNS Authoritative Server 自 **4.2 版起** 支援 **動態 DNS 記錄（LUA Records）**。

這些記錄中包含了小段設定程式（Lua 程式碼），  
可根據「查詢者 IP」、「EDNS Client Subnet」、「伺服器可用性」等因素，  
動態決定要回應哪個 IP 或 CNAME。

功能範圍從簡單到複雜，  
可實現多池（multi-pool）地理與權重負載平衡（Geo &amp; Weighted Load Balancing）。

雖然使用者不必了解內部原理，  
但 PowerDNS 的動態記錄本質上是 **嵌入在 DNS 內的小型 Lua 腳本**。

> 💡 注意：  
> 這是 **PowerDNS 專屬功能**，目前尚未成為 IETF 或其他標準機構的標準。  
> PowerDNS 官方承諾會致力於互通性，並希望最終能成為被廣泛支持的標準。

要啟用此功能，可在設定檔中加入：

```
enable-lua-records=yes

```

或對單一 zone 設定 metadata：

```
ENABLE-LUA-RECORDS=1

```

若要使用地理位置功能，請確認 `launch` 設定中包含：

```
launch=gpgsql,geoip

```

> ⚠️ 警告：  
> 使用 AXFR 傳輸包含長 Lua 記錄的 zone 時，請確保每筆記錄內容（TXT 型態傳輸）  
> 不超過 255 bytes。超過時會被分割成多段，導致次伺服器解析錯誤。

---

## 二、範例說明

### 1️⃣ ifportup()

```dns
www IN LUA A "ifportup(443, {'192.0.2.1', '192.0.2.2'})"

```

這會讓 `www` 這個名稱隨機回應 `192.0.2.1` 或 `192.0.2.2`，  
前提是這些 IP 的 **TCP 443 port 是開放的**。

若其中一個 IP 不再回應 443，則僅回傳另一個。  
若全部都不通，則全部地址會被回傳。

多組 IP 可分優先層級，例如：

```dns
www IN LUA A "ifportup(443, {{'192.0.2.1', '192.0.2.2'}, {'192.0.3.1'}})"

```

這表示：

- 若第一組中的任一 IP 可用 → 回第一組；
- 否則 → 試第二組。

因為 DNS 查詢要求極快回應，PowerDNS 不會即時檢查連線狀態，  
而是由背景程序定期檢測這些 IP 是否可用。

---

### 2️⃣ pickclosest()

```dns
www IN LUA A "pickclosest({'192.0.2.1','192.0.2.2','198.51.100.1'})"

```

這會根據查詢者的地理位置（GeoIP），  
選擇最靠近使用者的 IP 回覆。

也可以與 `ifportup()` 結合：

```dns
www IN LUA A ("ifportup(443, {'192.0.2.1', '192.0.2.2', '198.51.100.1'}, {selector='pickclosest'})")

```

這會在可用的 IP 中挑出距離使用者最近的一個。

---

### 3️⃣ 複雜 Lua 程式碼範例

```dns
www IN LUA A ";if country('US') then return {'192.0.2.1','192.0.2.2','198.51.100.1'} else return '192.0.2.2' end"

```

> 可同時回傳單一字串或多筆字串（array）。

---

### 4️⃣ 使用 qname 的例子

```dns
*.example.net 10 IN LUA TXT "; return 'Got a TXT query for ' .. qname:toString() .. '; First label is: ' .. qname:getRawLabels()[1]"

```

> 注意：`qtype` 無法從 Lua 內直接取得（它是固定的）。  
> 可使用預設變數（Preset Variables）取得其他查詢相關資訊。

---

## 三、在 SQL 後端使用 Lua Records

LUA Records 可與一般的 gmysql / gpgsql 通用 SQL 後端一起使用。

由於內容同時包含單引號與雙引號，  
在 `INSERT` 或 `UPDATE` 時需特別轉義。

範例如下：

```sql
-- 建立 zone
INSERT INTO domains (id, name, type) VALUES (1, 'example.com', 'NATIVE');

-- 啟用 Lua 記錄功能
INSERT INTO domainmetadata (domain_id, kind, content)
VALUES (1, 'ENABLE-LUA-RECORDS', 1);

-- 建立 pickclosest() 記錄
INSERT INTO records (domain_id, name, type, content, ttl)
VALUES (
  1,
  'www.example.com',
  'LUA',
  'A "pickclosest({''192.0.2.1'',''192.0.2.2'',''198.51.100.1''})"',
  600
);

```

這三筆 SQL：

- 建立 `example.com` zone，
- 啟用 Lua Records，
- 新增一筆 `www.example.com` 的動態 A 記錄。

---

## 四、LUA Record 格式

> 💡 名稱由來：  
> “Lua” 在葡萄牙文中意為「月亮」，**不是縮寫**。  
> 但 DNS 慣例記錄類型一律大寫，因此用 `LUA`。

一筆 LUA Record 結構如下：

```
LUA <query_type> <Lua 程式>

```

例如：

```dns
www IN LUA A "pickclosest({'1.1.1.1','8.8.8.8'})"

```

- 可設定 TTL，PowerDNS 會照常套用；
- 可被 DNSSEC 簽章（需在同一伺服器即時簽署）。

---

## 五、更強大的範例

```dns
west IN LUA A ("ifurlup('https://www.lua.org/', {{'192.0.2.1', '192.0.2.2'}, {'198.51.100.1'}}, {stringmatch='Programming in Lua'})")

```

這會：

- 嘗試對 `www.lua.org` 發出 HTTPS；
- 若回應內容包含 `Programming in Lua`，則視為可用；
- 第一組 IP 可用 → 回第一組；否則 → 回第二組。

與地理導向結合：

```dns
www IN LUA CNAME (";if(continent('EU')) then return 'west.powerdns.org' else return 'usa.powerdns.org' end")

```

對應的 `usa` 名稱配置如下：

```dns
usa IN LUA A ("ifurlup('https://www.lua.org/', {{'198.51.100.1'}, {'192.0.2.1', '192.0.2.2'}}, {stringmatch='Programming in Lua'})")

```

歐洲以外的訪客會優先使用 `198.51.100.1`，  
若失效則回到 `192.0.2.1` / `192.0.2.2`。

---

## 六、進階主題

### (1) 語法模式

若記錄內容未以 `;` 開頭，PDNS 會自動加上 `return`。  
若要撰寫完整 Lua 腳本，請以分號 `;` 開頭。

### (2) 可引用其他記錄的設定

可用 `include()` 引用共用設定：

```dns
config IN LUA LUA ("settings={stringmatch='Programming in Lua'} EUips={'192.0.2.1','192.0.2.2'} USAips={'198.51.100.1'}")

usa IN LUA A (";include('config') return ifurlup('https://www.lua.org/', {USAips, EUips}, settings)")
west IN LUA A (";include('config') return ifurlup('https://www.lua.org/', {EUips, USAips}, settings)")

```

---

## 七、安全與同步機制

- LUA Record 是查詢時即時生成；
- 可透過 AXFR 同步至其他 PDNS 伺服器；
- 但無法直接查詢（防止外部洩露負載邏輯）。

> ⚠️ **警告：**
> 
> - 絕對不要從不信任的來源匯入含 LUA Record 的 zone！  
>     惡意 Lua 可能導致伺服器崩潰甚至被接管。
> - 即使是可信來源也請使用 TSIG 保護 AXFR 傳輸。

若有 DNSSEC 簽章需求，  
LUA Record 必須由主伺服器即時簽署（無法預先簽署 zone）。

---

## 八、共享 Lua 狀態模式（shared mode）

預設情況下，PDNS 每次查詢都會建立新的 Lua 執行環境。  
這樣每筆 LUA Record 彼此獨立，不會污染變數。  
但建立 Lua state 很耗時。

可啟用共用模式：

```
enable-lua-records=shared

```

- 每個執行緒建立一個 Lua 狀態並重複使用；
- 約可提升 QPS 10 倍；
- 各執行緒 Lua 環境獨立（UDP 為多實例，TCP 共用一個）。

---

## 九、Reference（函式與物件類別）

PowerDNS 提供的 Lua 內建 API 包含：

### 預設變數（Preset Variables）

- `qname`, `qtype`, `remoteip`, `ednssubnet` 等

### 可用函式

- `getdns()`, `ifportup()`, `pickclosest()`, `country()`, `continent()`
- `ifurlup()`, `include()`, `pdnslog()`, 等

### 物件類別

- `DNSName`, `ComboAddress`, `DNSResourceRecord`, `DNSHeader`, `QType`
- `Netmask`, `ComboAddressSet`, `DNSRecord`, 及輔助函式群組

（這些內容在官方文件的「LUA Reference」章節中詳細列出）

---

## 🔚 總結

<table id="bkmrk-%E5%8A%9F%E8%83%BD-%E8%AA%AA%E6%98%8E-lua-record-pow"><thead><tr><th>功能</th><th>說明</th></tr></thead><tbody><tr><td>**LUA Record**</td><td>PowerDNS 內建的動態 DNS 回覆機制</td></tr><tr><td>**語言**</td><td>Lua</td></tr><tr><td>**用途**</td><td>智能選址、健康檢查、地理導向、服務偵測</td></tr><tr><td>**啟用方式**</td><td>`enable-lua-records=yes` 或 `ENABLE-LUA-RECORDS=1`</td></tr><tr><td>**安全性**</td><td>僅限信任 zone；建議使用 TSIG</td></tr><tr><td>**效能優化**</td><td>`enable-lua-records=shared` 可大幅提高 QPS</td></tr></tbody></table>

---

是否要我幫你把這篇翻譯版整理成 **可直接放進公司內部 Wiki 的 Markdown 版（含語法高亮與範例區塊）**？  
我可以自動加上標題層級、代碼框、注意區塊與翻譯對照欄位。

# 【PowerDns】dnsdist

傳遞來源ip  
[https://www.dnsdist.org/advanced/passing-source-address.html](https://www.dnsdist.org/advanced/passing-source-address.html)

```shell
addLocal("0.0.0.0:53")

-- Recursor 作為唯一後端
-- useClientSubnet=true 傳送ECS 來源端資訊 
newServer({ name="rec", address="127.0.0.1:5301", pool="recursor", useClientSubnet=true })

--設定ipv4顯示的位數 
setECSSourcePrefixV4(32)
setECSSourcePrefixV6(128)

-- 不加任何分流規則，全部都送 recursor
addAction(AllRule(), PoolAction("recursor"))
```

# Android

# 畫面投放至Mac

### 安裝

```shell
brew install scrcpy
brew install --cask android-platform-tools
```

手機 開啟usb偵錯

[![image-1676887629570.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-02/scaled-1680-/AZA6wXwK5LHeNysQ-image-1676887629570.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-02/AZA6wXwK5LHeNysQ-image-1676887629570.png)

mac 呼叫

```
scrcpy
```

---

### 使用chrome 偵錯webview

url: chrome://inspect/#devices

手機開至網頁，點選 inspect

[![image-1676887786524.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-02/scaled-1680-/0eA58q9dkrtyVEVE-image-1676887786524.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-02/0eA58q9dkrtyVEVE-image-1676887786524.png)

[![image-1676887972466.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-02/scaled-1680-/djPOgSz9v0pnnns5-image-1676887972466.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-02/djPOgSz9v0pnnns5-image-1676887972466.png)

---

### 疑難排解

- 如果電腦還是找不到裝置，或是指出裝置未經授權：請拔除 USB 傳輸線，然後在裝置上依序輕觸「設定」&gt;「開發人員選項」&gt;「撤銷 USB 偵錯授權」。將裝置重新連接至電腦。當系統提示時，請同意授權。

[![image-1724224046699.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-08/scaled-1680-/vEOSpTvOaWCrSmfp-image-1724224046699.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-08/vEOSpTvOaWCrSmfp-image-1724224046699.png)

[![image-1724224116007.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-08/scaled-1680-/WmzzBdlwiE0DQBpI-image-1724224116007.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-08/WmzzBdlwiE0DQBpI-image-1724224116007.png)

# BookStack

# BookStack .env 設定檔

```shell
# 是否開啟debug模式
APP_DEBUG=true
# 語系
APP_LANG=zh_TW
# 時區
APP_TIMEZONE=Asia/Taipei
# drawio 網址
DRAWIO=https://embed.diagrams.net/?embed=1&proto=json&spin=1
#匯出pdf wkhtmltopdf 安裝位置
WKHTMLTOPDF=/usr/local/bin/wkhtmltopdf


# Database details
DB_HOST=localhost
DB_DATABASE=bookstackdb
DB_USERNAME=bookstackuser
DB_PASSWORD=bookstackpass

#mail server
MAIL_DRIVER=smtp
# 郵件服務設定
# SMTP mail options
# 郵件主機
MAIL_HOST=10.2.1.168
# Port 號
MAIL_PORT=25
# 登入帳號
#MAIL_USERNAME=null
# 登入密碼
#MAIL_PASSWORD=null
# 認證方式，看使用何種 port 號 例如：ssl or tls
MAIL_ENCRYPTION=null
# 寄件者的帳號
MAIL_FROM=bookstack@fmt.com.tw
# 寄件者名稱
MAIL_FROM_NAME="BookStack"

```

# BookStack 匯出pdf

<div id="bkmrk-%E5%87%BA%E8%99%95%3Abookstack-%E5%8C%AF%E5%87%BA%E6%88%90pdf%E6%AA%94">- 出處:[BookStack 匯出成PDF檔](https://wiki.freedomstu.com/books/%E9%96%8B%E6%BA%90%E8%BB%9F%E9%AB%94%E5%AE%89%E8%A3%9D%E6%B5%81%E7%A8%8B/page/bookstack-%E5%8C%AF%E5%87%BA%E6%88%90pdf%E6%AA%94)

---

- 下載 Google Noto Fonts 字型
- [https://www.google.com/get/noto/#sans-hant](https://www.google.com/get/noto/#sans-hant)
- 建立字型目錄並將字型放入

</div><div id="bkmrk-mkdir--p-%2Fusr%2Fshare%2F">```
mkdir -p  /usr/share/fonts/chinese
```

- 安裝`ttmkfdir`

```
#centos7
yum install  ttmkfdir -y

#centos8
dnf install ttmkfdir -y
```

```
ttmkfdir -e /usr/share/X11/fonts/encodings/encodings.dir
```

- 修改字型設定檔

```
vim /etc/fonts/fonts.conf
```

```
#加入以下這一 段，存檔退出
<dir>/usr/share/fonts/chinese</dir>
```

- 重新整理記憶體中的字型快取，這樣就不用 reboot 重啟了

```
fc-cache
```

- 確認字型是否安裝

```
fc-list | grep chinese
```

- wkhtmltopdf (可至[官網](https://wkhtmltopdf.org/downloads.html)下載 rpm) 
    - centos7

```
yum install -y libpng libjpeg openssl icu libX11 libXext libXrender xorg-x11-fonts-Type1 xorg-x11-fonts-75dpi
wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox-0.12.6-1.centos7.x86_64.rpm
rpm -Uvh wkhtmltox-0.12.6-1.centos7.x86_64.rpm
```

- - centos8

```
dnf install -y libpng libjpeg openssl icu libX11 libXext libXrender xorg-x11-fonts-Type1 xorg-x11-fonts-75dpi
wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox-0.12.6-1.centos8.x86_64.rpm
rpm -Uvh wkhtmltox-0.12.6-1.centos8.x86_64.rpm
```

- 修改專案下.env (預設/var/www/html/BookStack.env)

```
#加入
WKHTMLTOPDF=/usr/local/bin/wkhtmltopdf 

#這是剛才wkhtmltopdf 所安裝的路徑
```

</div>

# CSS

# 【CSS】css demo 1

```CSS
body {font:13px/20px Microsoft JhengHei, Helvetica; margin:0px; padding:0px; font-family: Microsoft JhengHei}
select {font-family: Microsoft JhengHei, Helvetica;}

.login .msgArea {display:none}

.header {background-color:#E40080; height:124px; border-bottom:6px solid #FFDD33; text-align:center}
.wrapArea {width:1200px; margin:0px auto}

.header img {margin:29px 0px 12px}
.header h2 {margin:0px; padding:0px}
.header h2 span {width:204px; font:15px/20px Microsoft JhengHei, Helvetica; color:#FFFFFF; border-left:1px solid #FFFFFF; border-right:1px solid #FFFFFF; padding:0px 5px; display:inline-block}

.msgArea {background-color:#444444; height:48px; text-align:right}
.msgArea p {height:48px; font:13px/48px Microsoft JhengHei, Helvetica; color:#FFFFFF; margin:0px; padding:0px 20px 0px 0px; display:inline-block}
.msgArea p span {font:bold 13px/48px Microsoft JhengHei, Helvetica; padding:0px 0px 0px 10px}
.msgArea a {float:right; width:85px; height:48px; display:inline-block}
.msgArea a img {display:none}
.msgArea a img:first-child {display:inline-block}
.msgArea a:hover img {display:inline-block}
.msgArea a:hover img:first-child {display:none}

.footer {text-align:center; padding:15px 0px; border-top:1px solid #FFDD33}
.footer p {font:12px/18px Microsoft JhengHei, Helvetica; letter-spacing:1px; color:#706E6E; margin:0px; padding:0px}

/** 登入框 **/
.loginArea {width:290px; margin:100px auto}
.loginArea h1 {margin:0px 0px 10px; padding:0px 0px 10px; border-bottom:1px dashed #444444}
.loginArea dl,
.loginArea dl dt,
.loginArea dl dd {margin:0px; padding:0px}
.loginArea dl dd {padding:8px 0px; position:relative}
.loginArea dl dd b {width:80px; text-align:right; color:#444444; display:inline-block}
.loginArea dl dd input {height:25px; font:13px/25px Microsoft JhengHei, Helvetica; color:#000000; padding:0px 6px; border:1px solid #C2C0C0}
.loginArea dl dd .code[type="text"] {width:100px; margin:0px 26px 0px 0px}
.loginArea dl dd img {position:absolute; top:9px}
.loginArea dl dt {margin:21px 0px 0px}
.loginArea dl dt input[type="submit"] {background-color:#FD98D1; width:100%; height:40px; font:bold 20px/40px Microsoft JhengHei, Helvetica; color:#FFFFFF; border:0px; border-radius:5px; cursor:pointer}

/** 中間主文區 **/
.inner {padding:30px 0px}

/** 主選單 **/
.menuArea { background-color: rgba(13, 144, 144, 0.69); height:auto; margin:0px 0px 5px}
.menuArea ul {width:90%; height:auto; margin:0 30px 0 30px; padding:0px; list-style:none; display:inline-block}
.menuArea ul li {float:left; height:40px}
.menuArea ul li a {font:15px/40px Microsoft JhengHei, Helvetica; color:#FFFFFF; text-align:center; padding:0px 15px; border-left:1px dotted #f1f0f0; cursor:pointer; text-decoration: none;}
.menuArea ul li:first-child a {border-left:0px}
.menuArea ul li dl,
.menuArea ul li dd {margin:0px; padding:0px}
.menuArea ul li dl {background-color:#FFFFFF; margin:-4px 0px 0px; border:4px solid #444444; border-radius:5px; position:relative; z-index:1; display:none}
.menuArea ul li dd a {background:transparent url(http://mapp.momoshop.com.tw/mapp/images/icon_MenuDot.png) no-repeat 8px 16px ; height:auto; font:13px/20px Microsoft JhengHei, Helvetica; color:#706E6E; text-align:left; text-decoration:none; padding:10px 0px 10px 20px; border:0px; display:block; cursor:pointer}
.menuArea ul li dd a:hover {background-color:#E8E8E8; color:#E40080}
.menuArea ul li:hover dl {display:block}

/** 麵包屑 **/
.breadCrumb {background-color:#FFE7F4; height:20px; border-left:4px solid #E40080; margin:0px; padding:0px 0px 0px 5px}
.breadCrumb dl,
.breadCrumb dt,
.breadCrumb dd {float:left; margin:0px; padding:0px}
.breadCrumb dd {margin:0px 0px 0px 5px}
.breadCrumb dd span {margin:0px 5px 0px 0px}
.breadCrumb a {color:#000000; text-decoration:none}
.breadCrumb a:hover {color:#E40080}

/** 主文區 **/
.contentArea {margin:20px 0px 0px}

/** 搜尋條件 **/
.searchArea {padding:0px 0px 20px 0px; border-bottom:1px dotted #B1B0B0}
.searchArea ul {margin:0px; padding:0px; list-style:none; border-radius: 6px;}
.searchArea ul li {min-height:30px; margin:5px 0px; position:relative}
.searchArea ul li b {width:90px; height:25px; font:13px/28px Microsoft JhengHei, Helvetica; text-align:right; display:inline-block}
.searchArea ul li input {width:150px; height:25px; font:13px/25px Microsoft JhengHei, Helvetica; border:1px solid #C2C0C0; padding:0px 0px 0px 6px}
.searchArea ul li input[type="submit"] {background-color:#E40080; width:auto; height:27px; font:bold 13px/27px Microsoft JhengHei, Helvetica; color:#FFFFFF; margin:0px 0px 0px 5px; padding:0px 20px; border:0px; border-radius:3px; position:relative; top:-1px; cursor:pointer}
.searchArea ul li input[type="button"] {background-color:#E40080; width:auto; height:27px; font:bold 13px/27px Microsoft JhengHei, Helvetica; color:#FFFFFF; margin:0px 0px 0px 5px; padding:0px 20px; border:0px; border-radius:3px; position:relative; top:-1px; cursor:pointer}
.searchArea ul li input[type="file"] {width:180px; height:25px; font:13px/25px Microsoft JhengHei, Helvetica; border:0px; padding:0px 0px 0px 6px}
.searchArea ul li img {vertical-align:middle}
.searchArea ul li select {width:158px; padding:4px 6px; border:1px solid #C2C0C0}
.searchArea ul li select[class="small"] {width:80px; padding:4px 6px; border:1px solid #C2C0C0}
.searchArea ul li textarea {font:13px/20px Microsoft JhengHei, Helvetica; color:#4C4C4C; padding:5px 10px; border:1px solid #C2C0C0; resize:none}
.searchArea ul li input[name="promo_url"] {width:330px}
.pushDataArea input[type="submit"] {background-color:#E40080; width:auto; height:27px; font:bold 13px/27px Microsoft JhengHei, Helvetica; color:#FFFFFF; margin:0px 0px 0px 5px; padding:0px 20px; border:0px; border-radius:3px; position:relative; top:-1px; cursor:pointer;float: right;margin-right: 30px;top: 5px;}
.pushDataArea input[type="button"] {background-color:#E40080; width:auto; height:27px; font:bold 13px/27px Microsoft JhengHei, Helvetica; color:#FFFFFF; margin:0px 0px 0px 5px; padding:0px 20px; border:0px; border-radius:3px; position:relative; top:-1px; cursor:pointer;float: right;margin-right: 30px;top: 5px;}


/** 功能按鈕區 **/
.featuresBtnArea {width:100%; height:50px; display:inline-block}
.featuresBtnArea ul,
.featuresBtnArea ul li {float:right; margin:0px; padding:0px; list-style:none}
.featuresBtnArea ul {margin:11px 0px 0px}
.featuresBtnArea ul li input[type="button"] {background-color:#E40080; width:auto; height:27px; font:bold 13px/27px Microsoft JhengHei, Helvetica; color:#FFFFFF; margin:0px 0px 0px 5px; padding:0px 20px; border:0px; border-radius:3px; cursor:pointer}
.featuresBtnArea p {font:13px/50px Microsoft JhengHei, Helvetica; margin:0px; padding:0px; display:none}
.featuresBtnArea p span {color:#E40080; padding:0px 4px; display:none}

/** 查詢結果區 **/
.resultArea table th,
.resultArea table td {padding:5px 8px; text-align:center}
.resultArea table thead th {background-color:#5c5a5a; font:bold 13px/20px Microsoft JhengHei, Helvetica; color:#FFFFFF}
.resultArea tbody td {border-bottom:1px solid #BDBCBC}
.resultArea tbody td ul {margin:0px; padding:0px; list-style:none}
.resultArea tbody td ul li {display:inline-block; padding-bottom:3px;}
.resultArea tbody td ul li input {background-color:#FFDD33; font:13px/25px Microsoft JhengHei, Helvetica; color:#000000; margin:0px 5px; padding:3px 13px; border:0px; border-radius:5px; cursor:pointer}
.resultArea tbody td ul li input[disabled="true"] {background-color:#E8E8E8; color:#706E6E; cursor:default}
.resultArea tbody td input[type="number"] {text-align:right;margin:3px;width:70px;font-weight: bold;color: brown;}

.resultArea2 table th,
.resultArea2 table td {padding:5px 8px; text-align:center}
.resultArea2 table thead th {background-color:#444444; font:bold 13px/20px Microsoft JhengHei, Helvetica; color:#FFFFFF}
.resultArea2 tbody td {border-bottom:1px solid #BDBCBC; text-align: center;}
.resultArea2 tbody td:nth-child(2n+1) {background-color:#F1F0F0}
.resultArea2 tbody td:nth-child(15) {text-align:left;}
.resultArea2 tbody td:nth-child(19) {text-align:left;}
.resultArea2 tbody td:nth-child(22) {text-align:left;}
.resultArea2 tbody td:nth-child(2) {color: #9C9C9C;}
.resultArea2 tbody td:nth-child(26) {color: #9C9C9C;}
.resultArea2 tbody td p {color: #E40080; margin: 0;}
.resultArea2 tbody td p2 {color: #8000E4;}
.resultArea2 tbody td a {text-decoration: none;color: #0083e8;}

/** 頁次區 **/
.pageArea {display:none}
.pageArea ul {margin:20px 0px 0px; padding:0px; list-style:none}
.pageArea ul li {width:30px; height:30px; font:13px/31px Microsoft JhengHei, Helvetica; color:#999999; margin-right: 5px; text-align:center; border:1px solid #DDDDDD; display:inline-block; overflow:hidden; cursor:pointer}
.pageArea ul li:hover {background-color:#EAEAEA; color:#666666; border-color:#777777}
.pageArea ul .selected {background-color:#999999; font:bold 13px/31px Microsoft JhengHei, Helvetica; color:#FFFFFF; border-color:#999999}
.pageArea dl {float:right; margin:0px; padding:0px}
.pageArea dl dt,
.pageArea dl dd {height:30px; margin:0px 0px 0px 10px; padding:0px; display:inline-block}
.pageArea dl dt b {font:13px/31px Microsoft JhengHei, Helvetica; color:#E40080; padding:0px 4px}
.pageArea dl dd {font:13px/31px Microsoft JhengHei, Helvetica; color:#999999; padding:0px 10px; border:1px solid #DDDDDD; cursor:pointer}
.pageArea dl dd:hover {background-color:#EAEAEA; color:#666666; border-color:#777777}

/** 推播訊息浮層 **/
.pushDataArea {background-color:#FFFFFF; border-radius:10px; position:absolute; top:0px; left:0px; z-index:2; display:none}
.pushDataArea h2 {background-color:#444444; height:30px; font:bold 13px/30px Microsoft JhengHei, Helvetica; color:#FFFFFF; margin:0px; padding:0px 10px; border-radius:10px 10px 0px 0px}
.pushDataArea h2 a {float:right; height:30px; margin:0px -10px 0px 0px; padding:0px 15px; border-radius:0px 10px 0px 0px; cursor:pointer}
.pushDataArea h2 a:hover {background-color:#FFFFFF; color:#000000}
.pushDataArea .dataArea {padding:10px}
.pushDataArea .dataArea table th,
.pushDataArea .dataArea table td {background-color:#F1F0F0; height:40px; padding:5px 10px; border-bottom:1px solid #D1CFCF}
.pushDataArea .dataArea table th {background-color:#444444; width:60px; text-align:right; color:#FFFFFF; border-bottom:1px solid #FFFFFF; padding:5px 20px}
.pushDataArea .dataArea table td input {width:220px; height:25px; font:13px/20px Microsoft JhengHei, Helvetica; color:#4C4C4C; padding:0px 10px; border:1px solid #C2C0C0}
.pushDataArea .dataArea table td img {vertical-align:middle}
.pushDataArea .dataArea table td select {padding:3px 6px; border:1px solid #C2C0C0; position:relative; top:1px}
.pushDataArea .dataArea table td textarea {width:600px; font:13px/20px Microsoft JhengHei, Helvetica; color:#4C4C4C; padding:5px 10px; border:1px solid #C2C0C0; resize:none}
.pushDataArea .dataArea table td input[name="goodsUrl"] {width:600px}
.pushDataArea .dataArea table td input[disabled="disabled"] {background-color:#4682B4; color:#FFFFFF; border-color:#4682B4}
.pushDataArea .dataArea table #goodsCodeTr,
.pushDataArea .dataArea table #orderTimeTr,
.pushDataArea .dataArea table #fsCodeTr,
.pushDataArea .dataArea table #gaCodeTr {display:none}
.pushDataArea .btnArea {padding:10px 0px 20px; text-align:center}
.pushDataArea .btnArea input[type="button"] {background-color:#E40080; width:auto; height:27px; font:bold 13px/27px Microsoft JhengHei, Helvetica; color:#FFFFFF; margin:0px 0px 0px 5px; padding:0px 20px; border:0px; border-radius:3px; cursor:pointer}
.pushDataArea .dataArea table td input[type=file] {background-color:#FFFFFF; width:auto; border:0px; border-bottom:1px solid #C2C0C0; padding:0px}
.pushDataArea .dataArea table td input[type=button] {background-color:#E40080; width:auto; color:#FFFFFF; height:auto; border:1px solid #E40080; border-radius:4px; margin:0px 0px 0px 5px; cursor:pointer}

.sample {margin:0px 0px 0px 10px}

/**隱藏圖示的css **/
div.showLoading {padding:10px; position:absolute; top:0px; left:0px; z-index:5; display:none}
/**背景白幕的css **/
.whiteBk000 {background-color:#FFFFFF; opacity:0.5; filter:alpha(opacity=50); position:absolute; top:0px; left:0px; z-index:4;display:none;}

#gotop {
    display: none;
    position: fixed;
    right: 20px;
    bottom: 20px;
    padding: 10px 15px;
    font-size: 18px;
    background: #e63fa1;
    color: white;
    cursor: pointer;
    z-index: 99999;
    border-radius: 20px;
}

```

# 【Tailwind Css】相關

vscode extension

#### [![image-1671148286410.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-12/scaled-1680-/yZYEsS3AN6cTg36p-image-1671148286410.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-12/yZYEsS3AN6cTg36p-image-1671148286410.png)

取消 validate 驗證

[![image-1671148414650.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-12/scaled-1680-/ZeH3YPIsaVh3hBxe-image-1671148414650.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-12/ZeH3YPIsaVh3hBxe-image-1671148414650.png)

[![image-1671148382552.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-12/scaled-1680-/pI9g44alRdpuDN39-image-1671148382552.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-12/pI9g44alRdpuDN39-image-1671148382552.png)

初始化專案

```
npm install -D tailwindcss
npx tailwindcss init
```

#### 相關連結

[GitHub - aniftyco/awesome-tailwindcss: 😎 Awesome things related to Tailwind CSS](https://github.com/aniftyco/awesome-tailwindcss)

# CSS_斜切角實現

[纯CSS斜切角实现 - notes (gitbook.io)](https://xia-ao.gitbook.io/notes/yu-yan/css/xie-qie-jiao-shi-xian)

# 【CSS】inline, block, inline-block 比較

當然可以！以下是一份完整對比 `inline`、`block`、`inline-block` 以及其他 **相似概念的屬性（如 flex、grid）** 的整理，幫你從「排列方式、寬高設定、用途」等角度快速掌握它們的差異：

---

## ✅ display 類型比較總覽表

<table id="bkmrk-%E7%89%B9%E6%80%A7%2F%E9%A1%9E%E5%9E%8B-inline-block-i"><thead><tr><th>特性/類型</th><th>`inline`</th><th>`block`</th><th>`inline-block`</th><th>`flex`</th><th>`grid`</th></tr></thead><tbody><tr><td>**是否換行**</td><td>❌ 不會</td><td>✅ 會</td><td>❌ 不會</td><td>❌ 子元素可橫向排列</td><td>❌ 預設橫向排列</td></tr><tr><td>**可否設寬高**</td><td>❌ 不行（無效）</td><td>✅ 可以</td><td>✅ 可以</td><td>✅ 可以</td><td>✅ 可以</td></tr><tr><td>**子元素排列方式**</td><td>橫向排列</td><td>垂直排列</td><td>橫向排列</td><td>橫向（可變）</td><td>依格線排列（2D）</td></tr><tr><td>**是否可佔整行**</td><td>❌</td><td>✅</td><td>❌</td><td>❌（看內容/設定）</td><td>❌（看格線配置）</td></tr><tr><td>**適合用來**</td><td>文字、行內元素</td><td>區塊、段落、容器</td><td>輕量橫向組件（按鈕等）</td><td>排列多個項目（卡片、列）</td><td>複雜版面、表格、卡片區塊</td></tr><tr><td>**可否放 block 元素**</td><td>❌</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr></tbody></table>

---

## ✅ 說明重點

### 🔹 `inline`

- 僅佔**內容寬度**，不換行
- 無法設定寬高
- 適合用於 `<span>`, `<a>`, `<strong>` 等

---

### 🔹 `block`

- 自動換行，佔滿整行
- 可設定寬高
- 適合用於 `<div>`, `<p>`, `<section>` 等

---

### 🔹 `inline-block`

- 類似 `inline`，**可以水平排列**
- 同時擁有 `block` 的能力：可以設定寬高
- 適合做「按鈕、badge、圖片說明」等不佔整行的小元件

---

### 🔹 `flex`（父元素設 `display: flex`）

- 子元素**預設橫向排列**
- 可用 `justify-content`, `align-items` 控制對齊方式
- 適合用於**橫向 layout**, **排卡片、工具列**等

---

### 🔹 `grid`（父元素設 `display: grid`）

- 子元素依格線排列，可橫可縱
- 適合做**表格式、欄位分佈清楚**的 layout
- 高度彈性，可定義欄/列寬度、跨欄等

---

## ✅ 總結建議（選擇指引）

<table id="bkmrk-%E6%83%B3%E5%81%9A%E7%9A%84%E4%BA%8B-%E5%BB%BA%E8%AD%B0%E4%BD%BF%E7%94%A8-%E8%A1%8C%E5%85%A7%E6%94%BE%E6%96%87%E5%AD%97%EF%BC%88%E7%84%A1%E5%AF%AC%E9%AB%98%EF%BC%89"><thead><tr><th>想做的事</th><th>建議使用</th></tr></thead><tbody><tr><td>行內放文字（無寬高）</td><td>`inline`</td></tr><tr><td>單區塊、自然上下排列</td><td>`block`</td></tr><tr><td>要設定寬高又要橫向排列</td><td>`inline-block`</td></tr><tr><td>排列表現靈活（橫、對齊）</td><td>`flex`</td></tr><tr><td>網格狀、表格排版需求</td><td>`grid`</td></tr></tbody></table>

---

## `inline-block` 是否可以取代 `inline` ?

### ✅ 簡答：

**可以，但要小心**。`inline-block` 在大多數情況下可以取代 `inline`，但它們在**行為上仍有差異**，直接替換可能會造成版面細節不同。

---

## 🔍 詳細說明：`inline-block` 取代 `inline` 的注意事項

<table id="bkmrk-%E6%AF%94%E8%BC%83%E9%9D%A2%E5%90%91-inline-inline-b"><thead><tr><th>比較面向</th><th>`inline`</th><th>`inline-block`</th><th>差異是否影響？</th></tr></thead><tbody><tr><td>**橫向排列**</td><td>✅ 會</td><td>✅ 會</td><td>✅ 相同</td></tr><tr><td>**寬高控制**</td><td>❌ 無效</td><td>✅ 可控制寬高</td><td>✅ 有差異</td></tr><tr><td>**padding / margin 上下**</td><td>❌ 通常無效或不穩定</td><td>✅ 完全有效</td><td>✅ 有差異</td></tr><tr><td>**baseline 對齊**</td><td>✅ 與文字 baseline 對齊</td><td>⚠️ 有時偏移（可能上下出現間隙）</td><td>✅ 有差異</td></tr><tr><td>**line-height 影響**</td><td>✅ 受影響，與文字同行</td><td>✅ 也受影響，但更像方塊</td><td>⚠️ 可能導致意外間距</td></tr><tr><td>**內容包覆**</td><td>✅ 只包住文字寬度</td><td>✅ 包覆內容，也可設定寬高</td><td>✅ 相同</td></tr><tr><td>**應用範圍**</td><td>文字、icon 等純行內項目</td><td>小區塊元件：按鈕、badge、icon + 文字等</td><td>✅ 可取代但需測試</td></tr></tbody></table>

---

## ✅ 什麼情況可以直接用 `inline-block` 取代？

### ✔ 適合：

- 需要讓元素「並排」但又想控制**寬高、padding、border**
- 你不在意 **baseline 對齊細節**（或可調整）
- 你正在做「可重用的元件」如：按鈕、標籤、商品卡片

### ❌ 不適合：

- 像 `<span>` 這類**行內字串包裹**，如果替換成 `inline-block`，可能會造成行高偏移或上下出現奇怪的間距
- 想精準維持與文字「垂直對齊」的行為

---

## 🧪 實驗範例

```html
<p>
  Hello <span style="display:inline; background:yellow">inline</span> world!
</p>
<p>
  Hello <span style="display:inline-block; background:lightblue">inline-block</span> world!
</p>

```

[![image-1754373945717.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-08/scaled-1680-/Z7RtG5mGbap3wSdO-image-1754373945717.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-08/Z7RtG5mGbap3wSdO-image-1754373945717.png)

你會發現：

- `inline` 看起來比較貼齊文字
- `inline-block` 會有**高度感**，若沒特別控制 `vertical-align`，可能偏離 baseline

---

## 🎯 結論

<table id="bkmrk-%E5%95%8F%E9%A1%8C-%E7%AD%94%E6%A1%88-inline-block-%E5%8F%AF"><thead><tr><th>問題</th><th>答案</th></tr></thead><tbody><tr><td>`inline-block` 可以取代 `inline` 嗎？</td><td>✅ **可以，但不完全等效**</td></tr><tr><td>有什麼副作用？</td><td>⚠️ **行高變化、baseline 偏移、可能多出空隙**</td></tr><tr><td>要不要取代？</td><td>📌 **如果需要控制寬高 → 用 `inline-block`；只包字串就繼續用 `inline`**</td></tr></tbody></table>

---

# 【CSS】特殊屬性工具網站

要查詢這些 CSS 特殊屬性、瀏覽器前綴（Vendor Prefixes）以及相容性，以下這幾個網站是前端工程師必備的「軍火庫」：

<div aria-busy="false" aria-live="polite" id="bkmrk-"><div aria-busy="false" aria-live="polite">---

</div></div>### 1. Can I Use (最權威的相容性查詢)

這是全球開發者最常用的網站。只要輸入屬性名稱（例如 `scrollbar-width`），它就會用紅綠格子告訴你哪些瀏覽器支援、從哪個版本開始支援。

<div aria-busy="false" aria-live="polite" id="bkmrk-%E7%89%B9%E9%BB%9E%EF%BC%9A-%E6%8F%90%E4%BE%9B%E7%B2%BE%E7%A2%BA%E7%9A%84%E5%B8%82%E4%BD%94%E7%8E%87%E6%95%B8%E6%93%9A%EF%BC%8C%E8%AE%93%E4%BD%A0%E6%B1%BA%E5%AE%9A%E6%98%AF"><div aria-busy="false" aria-live="polite">- **特點：** 提供精確的市佔率數據，讓你決定是否需要寫備用方案（Fallback）。
- **網址：** [caniuse.com](https://caniuse.com/)

---

</div></div>### 2. MDN Web Docs (開發者的百科全書)

由 Mozilla 維護，是學習 Web 技術最完整的地方。除了教你怎麼用，它還會列出所有非標準的屬性（Non-standard）。

<div aria-busy="false" aria-live="polite" id="bkmrk-%E7%89%B9%E9%BB%9E%EF%BC%9A-%E5%A6%82%E6%9E%9C%E4%B8%80%E5%80%8B%E5%B1%AC%E6%80%A7%E5%89%8D%E9%9D%A2%E6%9C%89%E3%80%8C%E5%B0%8F%E9%BB%83%E8%89%B2%E9%A9%9A%E5%98%86%E8%99%9F"><div aria-busy="false" aria-live="polite">- **特點：** 如果一個屬性前面有「小黃色驚嘆號」或「燒瓶圖示」，就代表它是實驗性或非標準的屬性。
- **網址：** [developer.mozilla.org](https://developer.mozilla.org/)

---

</div></div>### 3. Autoprefixer (自動補全前綴的工具)

雖然不是查詢網站，但它是現代開發（如 Vite, Webpack, Next.js）背後的核心。你只需要寫標準的 CSS，它會自動幫你補上 `-webkit-`、`-ms-` 等前綴。

<div aria-busy="false" aria-live="polite" id="bkmrk-%E7%B7%9A%E4%B8%8A%E6%B8%AC%E8%A9%A6%E7%89%88%EF%BC%9A-autoprefixer-"><div aria-busy="false" aria-live="polite">- **線上測試版：** [Autoprefixer Online](https://autoprefixer.github.io/)
- **用法：** 你可以把你寫的 CSS 貼左邊，它會自動在右邊產出相容性最高的版本。

---

</div></div>### 4. CSS-Tricks (實戰攻略站)

這是一個著名的部落格，專門針對各種「難搞」的 CSS 問題寫深入淺出的文章。

<div aria-busy="false" aria-live="polite" id="bkmrk-%E7%89%B9%E9%BB%9E%EF%BC%9A-%E9%97%9C%E6%96%BC%E6%8D%B2%E8%BB%B8%EF%BC%88scrollbar%EF%BC%89%E7%9A%84"><div aria-busy="false" aria-live="polite">- **特點：** 關於捲軸（Scrollbar）的自定義，他們有一篇非常有名的攻略文：[The Current State of Styling Scrollbars](https://css-tricks.com/the-current-state-of-styling-scrollbars/)。
- **網址：** [css-tricks.com](https://css-tricks.com/)

---

</div></div>### 總結建議

<div aria-busy="false" aria-live="polite" id="bkmrk-%E6%9F%A5%E8%83%BD%E4%B8%8D%E8%83%BD%E7%94%A8%EF%BC%9A-%E5%8E%BB-can-i-use%E3%80%82-"><div aria-busy="false" aria-live="polite">- **查能不能用：** 去 **Can I Use**。
- **查怎麼寫、原理是什麼：** 去 **MDN**。
- **懶得自己寫前綴：** 設定好你的開發環境（如 Tailwind 或 PostCSS），讓 **Autoprefixer** 幫你處理。

</div></div>順帶一提，如果你是用 **VS Code** 開發，建議安裝一個叫 **"Can I Use"** 的擴充功能，這樣你在寫 CSS 時直接選取文字就能快速查詢，非常方便！

# DB相關

# 【Redis】相關

 0 - 15 共16個資料庫 0為預設，使用select 切換

Redis 為何快

使用記憶體，單執行序，多路io複用

- NIO
- BIO
- AIO

[![image-1674755191601.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-01/scaled-1680-/ssDFjB4pfwvxJCyb-image-1674755191601.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-01/ssDFjB4pfwvxJCyb-image-1674755191601.png)

[![image-1674754995171.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-01/scaled-1680-/BVBaSynCiqGt7KKB-image-1674754995171.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-01/BVBaSynCiqGt7KKB-image-1674754995171.png)

【尚硅谷】2021最新Redis 6教程分布式锁，秒杀实战

- youtube  
    [https://www.youtube.com/playlist?list=PLmOn9nNkQxJHsATQDTW7Ci6X-Rcypgk2G](https://www.youtube.com/playlist?list=PLmOn9nNkQxJHsATQDTW7Ci6X-Rcypgk2G)
- 相關筆記  
    [https://s8jl-my.sharepoint.com/personal/atguigu\_s8jl\_onmicrosoft\_com/\_layouts/15/onedrive.aspx?id=%2Fpersonal%2Fatguigu%5Fs8jl%5Fonmicrosoft%5Fcom%2FDocuments%2F%E5%B0%9A%E7%A1%85%E8%B0%B7Redis6%E8%A7%86%E9%A2%91%E8%AF%BE%E7%A8%8B&amp;ga=1](https://s8jl-my.sharepoint.com/personal/atguigu_s8jl_onmicrosoft_com/_layouts/15/onedrive.aspx?id=%2Fpersonal%2Fatguigu%5Fs8jl%5Fonmicrosoft%5Fcom%2FDocuments%2F%E5%B0%9A%E7%A1%85%E8%B0%B7Redis6%E8%A7%86%E9%A2%91%E8%AF%BE%E7%A8%8B&ga=1)

# 【Redis】相關指令

### 相關指令

連線

```shell
# redis-cli -h {$host} -p {$port} -a password
# ping 檢測服務正常
$redis-cli -h 127.0.0.1 -p 6379 -a "mypass"
redis 127.0.0.1:6379>
redis 127.0.0.1:6379> PING

PONG
```

使用redis-cli 進入redis

```shell
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> 
```

進入db (具有16個

數據庫（DB）。這些數據庫編號從0到15，用於存儲不同的數據)

```shell
root@62952ea40eb3:/data# redis-cli
127.0.0.1:6379> 
```

查詢key (線上環境勿用， 請用scan)

```shell
#keys pattern
127.0.0.1:6379[2]> keys *
1) "v5.25.1_autocompleteKeywordV3_ASU"
2) "v5.25.1_autocompleteKeywordV3_AU"
3) "v5.25.1_autocompleteKeywordV3_ASUS"
4) "v5.25.1_autocompleteKeywordV3_AUS"
5) "v5.25.1_autocompleteKeywordV3_AS"
6) "v5.25.1_autocompleteKeywordV3_A"
7) "v5.25.1_autocompleteKeywordV3_AUU"
127.0.0.1:6379[2]> 

######################################
127.0.0.1:6379[2]> keys *autocompleteKeywordV3*
1) "v5.25.1_autocompleteKeywordV3_\xe9\x80\x81\xe5\xa5\xbd\xe7\xa6\xae"
2) "v5.25.1_autocompleteKeywordV3_\xe5\xb8\xb8\xe6\x98\xa5"
3) "v5.25.1_autocompleteKeywordV3_ASUS"
4) "v5.25.1_autocompleteKeywordV3_\xe5\xb8\xb8"
5) "v5.25.1_autocompleteKeywordV3_\xe7\x9b\xb4\xe6\x92\xad"
6) "v5.25.1_autocompleteKeywordV3_\xe7\x9b\xb4"
7) "v5.25.1_autocompleteKeywordV3_\xe9\x80\x81\xe5\xa5\xbd"
8) "v5.25.1_autocompleteKeywordV3_\xe7\x9b\xb4\xe6\x92\xad\xe6\xb8\x85\xe5\x96\xae"
9) "v5.25.1_autocompleteKeywordV3_\xe9\x80\x81"
```

查詢 sacn

```shell
# scan {inidex} match {pattern}
#返回分为两个部分如上面的代码中， 1）代表下一次迭代的游标，2）代表本次迭代的结果集
#，注意如果返回游标为0就代表全部匹配完成。
127.0.0.1:6379> scan 0 MATCH tony* 
1) "42"
2)  1) "tony25"
    2) "tony2519"
    3) "tony2529"
    4) "tony2510"
    5) "tony2523"
    6) "tony255"
    7) "tony2514"
    8) "tony256"
    9) "tony2511"
   10) "tony15"
127.0.0.1:6379> scan 42 MATCH tony* COUNT 1000
1) "0"
2)  1) "tony3513"
    2) "tony359"
    3) "tony4521"
    4) "tony356"
    5) "tony30"
    6) "tony320"
    7) "tony3"
    8) "tony312"
    
#批次刪除key
#redis-cli --scan --pattern "key前缀*" | xargs -L {筆數} redis-cli del
redis-cli --scan --pattern "tony*" | xargs -L 1000 redis-cli del
```

查詢資料

```shell
#get {key}
127.0.0.1:6379[2]> get v5.25.1_autocompleteKeywordV3_ASUS
"\x1f\x8b\b\x00\x00\x00\x00\x00\x00\x00\xc4W\xfdo\xdbD\x18\xfeW,\xf7\xd7.\xb9;;\x8e]\t\xa1.t\xa5b\x0b\xd5R`R\xd5\x1f\\\xe7\xe6Z\x8d}\xc6\x1fm\xd6(R\xf9\x81\x8d\xa8\xa0ubSUm\x141\x01b\x821Mh\xab`|\xfc1[\xbe\xfe\x0b\xce\x8e\xed\xd8m\xb2\xe4\xba\xc2\xaaV\xba\xdc{y\xde\xe7\xb9{\xde\xf7\xae\r\xde\xc5\xce\x16v\x96\x96\xf99\x1e\x82\x1cD\xf4O\xa0\xbf\xfc,\xef`\xd7\xafy%R\xc54\x86\x00H\xa6\xae`\xd7U\xf5`6\x99Z\xa8k\xd8\xf6\x0cb\r&?\xf5\xb1s\xe3}\xd5\xddH\xbe\x8d\x04P\x04\x12\x94\x10\x02\x05ID\x17.(\xc5\"(\x14\x059\x80u}M\xa3\x98\xfc\x9c\xe7\xf8x\x96\xc7u\xac\xf9\x01\xd8\x8aa\xd2o\xcbq\x92\xcb\x86\xeb\xf1s\xab\r^S=\\V\x83\x18?_\xf9\xa8\xc2\xf5n?\xe9>|D\x81\xb6\x89S5,\x9d\xce\xb7\xbf\xfe\xac\xdb\xfa\xb2sp\xd0k\xfd\xd8\xfe\xe3`\x18+\x91\x1aq\xe8\x82\x19A\x90\xa4K\x97h\xc0\xd0B\xda\x1b\x9eg\xbbs\xf9\xbca\xeab\xce$&q7\x88\x9d\xd3\x88\x99\xf3\xb6\xf3X3\x83@^3\xcd\xfc\xba\xa3Z\xd5|\x8d\xe8$\x8f \x10\x05\x10\xfc\xe4lK\x7f\xd7{\aJ\xb2,\t\xb0\x88\xa8\xc8@\x99\xaa\r6\xa5\x11\x8dVn\xd8\x945\x04q\xe4c\xb5\xe6\x87\x1b\x94\x00\xf1\xcd\xe6\xeci\x85\x9d\xd6^\xe7\xd1\xb7oE!\x92\x94\xf3Q\x18\x03\x8dVx\xf5\xc3\xc5\xfe\xfd\x17\xdd\x9f\x9f\x9f\x9fHaz\x91\x82\\<\x1f\x911\xd0@\xa4\xee\x10\xdf.\x13\x1a\x01\x90\xc2x\xb8N\r\xcc\xab\xae\xefr[\xc6\x16Y'd\x93\xb3\x1d\xc2A\x89#5\\\xa5K4\x8b\xaeP\x8at@\xfc`\xc8+\xc5\xb1\xf9Ex2\xffx\xe4fPe\xba\x8e\xdd`9\xad\xb4\xd5\xb5\t\xfc\xa0\x98\xa1\x04%0\xe4D?\xb0\x92\x8a\xe1N\xf1h\xc4Y?1\xac*\xd9\xa6+\xe1Y\xb1\x83\xce\xe19j\x14k\xf0T\xbf\x8d\x1d\xcf\xc0\x83<\xe6\xbc\xe7]\xa6C\xfe\xd5_\x0f:?<\xee\xfe\xf6\xa2\xfb\xeci\xd0}\xa2\xf9\xd54\x85\xb5Y\xde\"#\x0233\xf3\x00@e\x91\x1e1*\xd1\x91\xc4\xaf5\xd7\x9a\xe1aG:\x96,\x0f\xd7\xb8\x12q0g0\x9f\x1d\xab\x94\xde\xe1\xbd\xee\xfe\xcd\xf6\xe1O\x19\x1dY\nY)\x99X\xa8FL\xd4\x80B\xa4\xe65\xe6\xe8\x7f\xf5\xac{\xff\xfbW\xff|\xd3{|\xdc\xbb\xf9\x80\x0bi{\xb6\x1f\x1bE\x90R\xe6\xa5\x1f\x18v`,\xf4x\xd3,\xec\xd09m\xf3<\x92\x9c\xdc\xf2\xb0?\x0c\x92\x85\xc3\xa8Y\r3\x0efIx\xbdAz\x99!\x01\xd2\x0b\x0e\n\xb4\x97\xf3\x93v1\xcc\xba\xae\x88\x05p]Mv\x0e\xa56\x0e\xb1:'\x06\x1b\xbfW\x05\x00\x16/\x9e\x15vZCV*\xef\xb5\x7f\xfd\xbd\x7f\xebv\xc6\x90\x83\xd4Y#\x86s\xa1\x01%\x14\x18\x10R\x03\"q\xb2\x01CZ&\xbdJL-iNJ\xaa7)\xac\x12#,\xe6\x0e\xb9\x83\xad\xb0\xcd\xd2\x9a\xf5\xeb\x82\b\xd0Nr\x94b\xaa\x06D\xe6&0\n\x98\x85\x9d\x8d\xf5\x81\xab\x1d\xd5\xa8q\"\xa7{\xf5\x98\x18\x92\x87\xc4\x90\xcc@l\x14\xe6x\xab\x95\x97>X\xe0z\xbb\xfb\xed\xfd\xd6\x1b\xe6\x98\xb2*\xb3\t3\x95)\x01\x19\x06\xf5)#\xa5\x00&W\xe6\xcb\xdd}\xcb\xd8\xc4\x11\xd8\xcb\xdd;\x9d\xcf\x1f\xf6\x8e\xef\xf4\x8f\xf68\xd5p\xb8\x1dBL.\"\x9a\x14\xae\x90ny\x02\xcby\xb3dc6\xa8Ct\xce\xde \x16Nl)I)_J\x12\xab1\x87\x80\xcc\\<\xff:\xa7\xabf\xf0\xaa\x8b\xc9\xc80EFf\xbe\xf5S\x88\xe3\x8dxu\xe5\x1a'\x02z\xd5\xb7\xbf8x\x83\x04\xd3v\xbf\xce\xe1\xf3n\xebV\xa6\xf5e\x18d;`:\x146BA\x8eob\x05\x9c~W,W\xfe\a\x05\xfd\xef\x8e\xdaG{\xd9\xd7\x04M|\xe2\r\xb1\\\t\xf9\x02H\xf9B\x10\xbc\x1c\xe0\xe4\xc6\xcd\xe0u.|u\x0fz;*\xa4\x9a;*\xfc7\xc5\x15$d\xf6t\xef\xf8I\xf7\xee\xd3\x84(\x14RD\xc3\xff\xa0\x99\x0e+B{MS=\x9b\x8d\x13\x96SZ\xf8\x97\xbf\xdb\x7ffo\xef\xf2H\xf7\x96S\xc6\x05\x89q\x13#\x0c\x8d;_z[\xc4\x93\xccY\xe6\xf1\xf4I\xea(\xf10]\x9f9\xf7\xe6\xbf\x00\x00\x00\xff\xff\x03\x00\xd6\xdc\x935(\x11\x00\x00"

# 設定ttl
# expire {key} {秒數}
expire v5.25.1_autocompleteKeywordV3_ASUS 60

#獲得值的ttl
#ttl {key}
ttl v5.25.1_autocompleteKeywordV3_ASUS

```

查詢該DB 資料總數

```shell
# dbsize 該db儲存數
127.0.0.1:6379[2]> dbsize
(integer) 9
```

刪除該DB 所有資料

```shell
#清除目前db 所有資料
# flushdb

# 原本db有8筆資料
127.0.0.1:6379[2]> dbsize
(integer) 8

# 清除db
127.0.0.1:6379[2]> flushdb
OK

# 重新查詢已無資料
127.0.0.1:6379[2]> dbsize
(integer) 0
```

刪除該db資料

```shell
# del key1 key2 ... 
127.0.0.1:6379[2]> keys *
1) "v5.25.1_autocompleteKeywordV3_\xe9\x80\x81\xe5\xa5\xbd\xe7\xa6\xae"
2) "v5.25.1_autocompleteKeywordV3_\xe5\xb8\xb8\xe6\x98\xa5"
3) "v5.25.1_autocompleteKeywordV3_ASUS"
4) "v5.25.1_autocompleteKeywordV3_\xe5\xb8\xb8"
5) "v5.25.1_autocompleteKeywordV3_\xe7\x9b\xb4\xe6\x92\xad"
6) "v5.25.1_autocompleteKeywordV3_\xe7\x9b\xb4"
7) "v5.25.1_autocompleteKeywordV3_\xe9\x80\x81\xe5\xa5\xbd"
8) "v5.25.1_autocompleteKeywordV3_\xe7\x9b\xb4\xe6\x92\xad\xe6\xb8\x85\xe5\x96\xae"
9) "v5.25.1_autocompleteKeywordV3_\xe9\x80\x81"
127.0.0.1:6379[2]> del v5.25.1_autocompleteKeywordV3_ASUS
(integer) 1
127.0.0.1:6379[2]> keys *
1) "v5.25.1_autocompleteKeywordV3_\xe9\x80\x81\xe5\xa5\xbd\xe7\xa6\xae"
2) "v5.25.1_autocompleteKeywordV3_\xe5\xb8\xb8\xe6\x98\xa5"
3) "v5.25.1_autocompleteKeywordV3_\xe5\xb8\xb8"
4) "v5.25.1_autocompleteKeywordV3_\xe7\x9b\xb4\xe6\x92\xad"
5) "v5.25.1_autocompleteKeywordV3_\xe7\x9b\xb4"
6) "v5.25.1_autocompleteKeywordV3_\xe9\x80\x81\xe5\xa5\xbd"
7) "v5.25.1_autocompleteKeywordV3_\xe7\x9b\xb4\xe6\x92\xad\xe6\xb8\x85\xe5\x96\xae"
8) "v5.25.1_autocompleteKeywordV3_\xe9\x80\x81"
127.0.0.1:6379[2]> 
```

刪除所有redis資料

```shell
# 清除整台redis 資料
127.0.0.1:6379[2]> flushall
OK
```

---

#### 寫入資料(字串)

[![image-1698484020280.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-10/scaled-1680-/FoLdeenEnVJKiJTT-image-1698484020280.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-10/FoLdeenEnVJKiJTT-image-1698484020280.png)

```
set <key> <value>: 寫入資料
NX：當數據庫中的鍵不存在時，可以將鍵-值添加到數據庫
XX：當數據庫中的鍵存在時，可以將鍵-值添加到數據庫，與 NX 參數互斥
EX：鍵的超時秒數
PX：鍵的超時毫秒數，與 EX 互斥
get <key>：查詢對應鍵值
append <key> <value>：將給定的 <value> 追加到原值的末尾
strlen <key>：獲得值的長度
setnx <key> <value>：只有在鍵不存在時設置鍵的值

incr <key>：將鍵中儲存的數字值增加1
只能對數字值操作，如果為空，新增值為1
decr <key>：將鍵中儲存的數字值減少1
只能對數字值操作，如果為空，新增值為-1
incrby / decrby <key> <步長>：將鍵中儲存的數字值增減。自定義步長。

mset <key1> <value1> <key2> <value2> ...：同時設置一個或多個鍵-值對
mget <key1> <key2> <key3> ...：同時獲取一個或多個值
msetnx <key1> <value1> <key2> <value2> ...：同時設置一個或多個鍵-值對，當且僅當所有給定的鍵都不存在。原子性，有一個失敗則都失敗

getrange <key> <起始位置> <結束位置>：獲得值的範圍，類似於 Java 中的 substring，前包，後包
setrange <key> <起始位置> <value>：用 <value> 覆蓋 <key> 所儲存的字符串值，從 <起始位置> 開始（索引從0開始）。

setex <key> <過期時間> <value>：設置鍵值的同時，設置過期時間，單位秒。
getset <key> <value>：以新換舊，設置了新值同時獲得舊值。
```

#### 寫入資料(List)

```
lpush/rpush <key> <value1> <value2> <value3> ...：從左邊/右邊插入一個或多個值。
lpop/rpop <key>：從左邊/右邊吐出一個值。值在鍵在，值光鍵亡。

rpoplpush <key1> <key2>：從 <key1> 列表右邊吐出一個值，插到 <key2> 列表左邊。

lrange <key> <start> <stop>：按照索引下標獲得元素（從左到右）。
lrange mylist 0 -1：0 左邊第一個，-1 右邊第一個，（0-1 表示獲取所有）。
lindex <key> <index>：按照索引下標獲得元素（從左到右）。
llen <key>：獲得列表長度。

linsert <key> before <value> <newvalue>：在 <value> 的後面插入 <newvalue>。
lrem <key> <n> <value>：從左邊刪除 n 個 value（從左到右）。
lset <key> <index> <value>：將列表 key 下標為 index 的值替換成 value。
```

#### 寫入資料(Set)

```
sadd <key> <value1> <value2> ...：將一個或多個 member 元素加入到集合 key 中，已經存在的 member 元素將被忽略。
smembers <key>：取出該集合的所有值。
sismember <key> <value>：判斷集合 <key> 是否含有該 <value> 值，有1，沒有0。
scard <key>：返回該集合的元素個數。
srem <key> <value1> <value2> ...：刪除集合中的某個元素。
spop <key>：隨機從該集合中吐出一個值。
srandmember <key> <n>：隨機從該集合中取出 n 個值。不會從集合中刪除。
smove <source> <destination> <value>：將集合中一個值從一個集合移動到另一個集合。
sinter <key1> <key2>：返回兩個集合的交集元素。
sunion <key1> <key2>：返回兩個集合的聯集元素。
sdiff <key1> <key2>：返回兩個集合的差集元素（key1 中的，不包含 key2 中的）。
```

#### 寫入資料(Zset)

```
zadd <key> <score1> <value1> <score2> <value2>…：將一個或多個 member 元素及其 score 值加入到有序集 key 當中。
zrange <key> <start> <stop> [WITHSCORES]：返回有序集 key 中，下標在 <start> 到 <stop> 之間的元素。帶有 WITHSCORES，可以讓分數一起和值返回到結果集。
zrangebyscore <key> <minmax> [withscores] [limit offset count]：返回有序集 key 中，所有 score 值介於 min 和 max 之間（包括等於 min 或 max）的成員。有序集成員按 score 值遞增（從小到大）次序排列。
zrevrangebyscore <key> <maxmin> [withscores] [limit offset count]：同上，改為從大到小排列。
zincrby <key> <increment> <value>：為元素的 score 加上增量。
zrem <key> <value>：刪除該集合下，指定值的元素。
zcount <key> <min> <max>：統計該集合，分數區間內的元素個數。
zrank <key> <value>：返回該值在集合中的排名，從0開始。
```

#### 寫入資料(Hash)

```
hset <key> <field> <value>：給 <key> 集合中的 <field> 鍵賦值 <value>
hget <key1> <field>：從 <key1> 集合 <field> 取出 value
hmset <key1> <field1> <value1> <field2> <value2>...：批量設置 hash 的值
hexists <key1> <field>：查看哈希表 key 中，給定域 field 是否存在。
hkeys <key>：列出該 hash 集合的所有 field
hvals <key>：列出該 hash 集合的所有 value
hincrby <key> <field> <increment>：為哈希表 key 中的域 field 的值加上增量 1 或 -1
hsetnx <key> <field> <value>：將哈希表 key 中的域 field 的值設置為 value，當且僅當域 field 不存在。
```

### 相關資源

- [在線練習 https://try.redis.io/](https://try.redis.io/)
- [文檔總覽 http://www.redis.cn/documentation.html](http://www.redis.cn/documentation.html)
- [命令文檔 http://www.redis.cn/commands.html](http://www.redis.cn/commands.html)
- [docker中安装并配置redis https://cloud.tencent.com/developer/article/1670205](https://cloud.tencent.com/developer/article/1670205)
- [【Redis】用Docker-Compose搭建Redis Sentinel](https://lipeng1667.github.io/2021/06/21/set-up-redis-with-replication-and-sentinel-using-docker-compose/ "【Redis】用Docker-Compose搭建Redis Sentinel")
- [【Redis】用Docker-Compose搭建Redis主从复制](https://lipeng1667.github.io/2021/06/20/install-redis-with-docker-compose-and-configurate-as-raplication/ "【Redis】用Docker-Compose搭建Redis主从复制")

# 資料庫型態比較

**DBMS DATA TYPE**

<table id="bkmrk-data-type-sql-92-ora" style="font-weight: 400;"><tbody><tr><td width="115">**DATA TYPE**</td><td width="144">**SQL 92**</td><td width="144">**Oracle**</td><td width="144">**MySQL**</td><td width="144">**POSTGRE**</td><td width="144">**M$ SQL**</td><td width="144">**Common**</td></tr><tr><td width="115">**BOOLEAN**</td><td width="144">BOOLEAN</td><td width="144"> </td><td width="144"> </td><td width="144">BOOL</td><td width="144"> </td><td width="144"> </td></tr><tr><td width="115">**CHARACTER**</td><td width="144">CHAR  
VARCHAR</td><td width="144">CHAR  
VARCHAR2 NVARCHAR2  
LONG  
TEXT  
ENUM  
SET</td><td width="144">CHAR  
VARCHAR  
BLOB  
TEXT  
ENUM  
SET</td><td width="144">CHAR  
VARCHAR  
TEXT  
NAME  
CHARACTER</td><td width="144">CHAR  
VARCHAR  
TEXT</td><td width="144">CHAR  
VARCHAR  
TEXT</td></tr><tr><td width="115">**BIT**</td><td width="144">BIT  
BIT VARYING</td><td width="144"> </td><td width="144"> </td><td width="144"> </td><td width="144">BIT</td><td width="144"> </td></tr><tr><td width="115">**EXACT NUMERIC**</td><td width="144">INTEGER  
SMALLINT  
DECIMAL  
NUMERIC</td><td width="144">INTEGER  
NUMBER  
NUMERIC  
SMALLINT  
DECIMAL</td><td width="144">INTEGER  
SMALLINT  
DECIMAL NUMERIC</td><td width="144">INTEGER  
DECIMAL  
INT2  
INT4  
INT8  
NUMERIC  
SERIAL</td><td width="144">INTEGER  
BIGINT  
SMALLINT  
TINYINT  
NUMERIC  
DECIMAL</td><td width="144">INTEGER  
NUMERIC  
DECIMAL</td></tr><tr><td width="115">**APPROXIMATE NUMERIC**</td><td width="144">FLOAT  
REAL  
DOUBLE PRECISION</td><td width="144">FLOAT  
REAL  
DOUBLE  
PRECISION</td><td width="144">FLOAT  
REAL  
DOUBLE  
PRECISION</td><td width="144">FLOAT  
REAL  
DOUBLE PRECISION  
FLOAT4  
FLOAT8</td><td width="144">FLOAT  
REAL  
DOUBLE PRECISION</td><td width="144">FLOAT  
REAL  
DOUBLE RECISION</td></tr><tr><td width="115">**DATETIME**</td><td width="144">DATE  
TIME  
TIMESTAMP</td><td width="144">DATE  
TIME  
DATETIME  
TIMESTAMP  
YEAR</td><td width="144">DATE  
TIME  
DATETIME  
TIMESTAMP  
YEAR</td><td width="144">DATE  
TIME  
TIMETZ  
TIMESTAMP  
CURRENT  
EPOCH  
INFINITY  
INVALID  
NOW  
TODAY  
TOMORROW  
YESTERDAY</td><td width="144">DATE  
TIME  
SMALLDATATIME  
TIMESTAMP  
UNIQUEIDENTIFIER</td><td width="144">TIMESTAMP  
DATE</td></tr><tr><td width="115">**INTERVAL**</td><td width="144">INTERVAL</td><td width="144"> </td><td width="144"> </td><td width="144">INTERVAL</td><td width="144"> </td><td width="144"> </td></tr><tr><td width="115">**BINARY**</td><td width="144"> </td><td width="144">ROWID  
NROWID  
RAW  
LONG RAW</td><td width="144"> </td><td width="144"> </td><td width="144">BINARY  
VARBINARY  
IMAGE</td><td width="144"> </td></tr><tr><td width="115">**LARGE OBJECTS**</td><td width="144">CHARACTER LARGE OBJECTBINARY LARGE OBJECT</td><td width="144">CHARACTER LARGE OBJECT BINARY LARGE OBJECT NATIONAL LARGE OBJECT</td><td width="144"> </td><td width="144"> </td><td width="144"> </td><td width="144"> </td></tr><tr><td width="115">**NET**</td><td width="144"> </td><td width="144"> </td><td width="144"> </td><td width="144">CIDR  
INET</td><td width="144"> </td><td width="144"> </td></tr><tr><td width="115">**Geometric**</td><td width="144"> </td><td width="144"> </td><td width="144"> </td><td width="144">BOX  
CIRCLE  
LINE  
LSEG  
PATH  
POINT  
POLYGON  
SERIAL</td><td width="144"> </td><td width="144"> </td></tr></tbody></table>

[來源：資料型態整理 Oracle, MySQL, PostgreSQL, M$ SQL - DBMS DATA TYPE - Soul &amp; Shell Blog (toright.com)](https://blog.toright.com/posts/27/%E8%B3%87%E6%96%99%E5%9E%8B%E6%85%8B%E6%95%B4%E7%90%86-oraclemysqlpostgrem-sql-dbms-data-type.html)

# DB  相關資源

- [ DB 排名 https://db-engines.com/en/ranking](https://db-engines.com/en/ranking)

# 【Debian】連線MariaDB

在Debian上安裝ODBC連接MariaDB，您可以按照以下步驟進行操作：

1. 確保您的Debian系統已連接到互聯網，並且具有管理員權限（或以root用戶身份登錄）。
2. 打開終端，執行以下命令更新匯總列表：
    
    <div><div></div><div>`sudo apt <span class="hljs-keyword">update</span>`</div></div>
3. 安裝ODBC驅動程序及相關工具：<button class="flex ml-auto gap-2"></button>
    
    <div><div>`sudo apt install unixODBC unixODBC-dev`</div></div>
4. 下載並安裝MariaDB的ODBC驅動程序。首先，訪問MariaDB的官方下載頁面（[https://downloads.mariadb.org/connector-odbc/）並選擇適合您Debian版本的驅動程序。然後使用wget命令下載驅動程序的安裝包。請選擇以下命令中的URL替換為您的驅動程序的下載鏈接：](https://downloads.mariadb.org/connector-odbc/%EF%BC%89%E5%B9%B6%E9%80%89%E6%8B%A9%E9%80%82%E5%90%88%E6%82%A8Debian%E7%89%88%E6%9C%AC%E7%9A%84%E9%A9%B1%E5%8A%A8%E7%A8%8B%E5%BA%8F%E3%80%82%E7%84%B6%E5%90%8E%E4%BD%BF%E7%94%A8wget%E5%91%BD%E4%BB%A4%E4%B8%8B%E8%BD%BD%E9%A9%B1%E5%8A%A8%E7%A8%8B%E5%BA%8F%E7%9A%84%E5%AE%89%E8%A3%85%E5%8C%85%E3%80%82%E8%AF%B7%E5%B0%86%E4%BB%A5%E4%B8%8B%E5%91%BD%E4%BB%A4%E4%B8%AD%E7%9A%84URL%E6%9B%BF%E6%8D%A2%E4%B8%BA%E6%82%A8%E9%80%89%E6%8B%A9%E7%9A%84%E9%A9%B1%E5%8A%A8%E7%A8%8B%E5%BA%8F%E7%9A%84%E4%B8%8B%E8%BD%BD%E9%93%BE%E6%8E%A5%EF%BC%9A)
    
    <div><div></div><div>`<span class="xml">wget <span class="hljs-tag"><<span class="hljs-name">驱动程序的下载链接</span>></span></span>`</div></div>
5. 解壓驅動程序的安裝包。使用以下命令，將`<package-name>`替換為您下載的安裝包的名稱：
    
    <div><div></div><div>`tar xvf <<span class="hljs-keyword">package</span>-name>.tar.gz`</div></div>
6. 進入解壓後的目錄：
    
    <div><div></div><div>`<span class="hljs-built_in">cd</span> <package-name>`</div></div>
7. 使用以下命令執行驅動程序的安裝。請注意，這裡的命令可能會因驅動程序的版本和您系統的配置而有所不同。確保按照驅動程序的安裝說明進行操作。
    
    <div><div></div><div>`sudo ./configuresudo <span class="hljs-built_in">make</span>sudo <span class="hljs-built_in">make</span> install`</div></div>
8. 配置ODBC連接。使用文本編輯器（如nano或vi）打開ODBC配置文件`/etc/odbcinst.ini`：
    
    <div><div></div><div>`sudo nano /etc/odbcinst.ini`</div></div>
9. 在配置文件中添加以下內容，以指定MariaDB的ODBC驅動程序：
    
    <div><div></div><div>`[MariaDB]Description = MariaDB ODBC driverDriver = <驱动程序的完整路径>`</div></div>命令`<驱动程序的完整路径>`替換為您安裝的驅動程序的實際路徑。保存並關閉文件。
10. 配置ODBC數據源。使用文本編輯器打開ODBC數據源配置文件`/etc/odbc.ini`：
    
    <div><div></div><div>`sudo nano /etc/odbc.ini`</div></div>
11. 在文件中添加以下內容，以指定您的MariaDB數據庫連接信息：
    
    <div><div></div><div>`[ODBC Data Source]Driver = MariaDBServer = <数据库服务器地址>Port = <数据库端口号>Database = <数据库名称>User = <用户名>Password = <密码>`</div></div>將`<数据库服务器地址>`、`<数据库端口号>`、`<数据库名称>`、`<用户名>`和`<密码>`替換為您實際的數據庫連接信息。保存並關閉文件。
12. 現在，您應該能夠使用ODBC連接到MariaDB數據庫了。您可以使用ODBC工具或編程語言中的ODBC庫來建立連接並執行查詢。

請注意，這些步驟提供了基本的安裝和配置過程，具體取決於您使用的驅動程序版本和系統配置。您可以根據驅動程序的安裝說明進行操作，並在遇到問題時參考相關文檔和資源。

# 【Redis】設定相關

/etc/redis.conf

or

/usr/local/etc/redis/redis.conf

- #### 開放外部連線

```INI
# 只支持本地
bind 127.0.0.1 -::1
protected-mode yes

# 要支援遠端存取註解 bind 127.0.0.1 -::1 
# protected-mode no
# bind 127.0.0.1 -::1
protected-mode no
```

#### 其他設定

```INI
# 未完成三向交握＋已完成三向交握上限
tcp-backlog 511

# Close the connection after a client is idle for N seconds (0 to disable)
# 0: 永不過期
timeout 0

# 檢測連線存在的間隔時間
tcp-keepalive 300

# 使用daemon啟動
daemonize yes

# pid檔案位置
# 如果指定了pid文件，Redis在啟動時會將其寫入指定位置，並在退出時刪除它。
# 當服務器以非守護模式運行時，如果在配置中未指定pid文件，則不會創建pid文件。當服務器以守護模式運行時，即使未指定，也將使用pid文件，默認為 "/var/run/redis.pid"。
# 創建pid文件是盡力而為的：如果Redis無法創建它，不會發生任何壞事，服務器將正常啟動和運行。
# 請注意，在現代Linux系統上，應改用 "/run/redis.pid"，因為這更符合規範。
pidfile /var/run/redis_6379.pid

# log 級別 
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel notice

# 設定db數量
databases 16

# 設置密碼
# requirepass {密碼}
requirepass foobared

# 使用命令臨時設定密碼(服務重啟失效)
127.0.0.1:6379>config set requirepass "123456"
# 登入
127.0.0.1:6379>auth 123456

# 最大連接數(預設無)
maxclients 10000
# 最大記憶體使用量(預設無)
maxmemory <bytes>


```

#### 持久化(快照)(RDB)

```INI
# 持久化(快照)(REB)
# 有可能最後一次資料丟失
# 寫入檔名
dbfilename dump.rdb
# 寫入目錄(啟動redis命令當前目錄)
dir ./
# 快照設定

# 3600秒內有1個key異動
# save 3600 1
# 300秒內有100個key異動
# save 300 100
# 60秒內有10000個key異動
# save 60 10000

# Redis 無法寫入檔案，關閉寫入操作
stop-writes-on-bgsave-error yes
# 文件進行壓縮
rdbcompression yes
# 文件檢查(影響10%效能)
rdbchecksum yes

# 如何恢復 ：
# 關閉 redis 將原先dump.rdb 放回執行目錄下，啟動 redis，會自動載入資料
```

#### 持久化(AOF)

使用log方式存放，只記錄寫的指令，不紀錄讀

<span style="background-color: #ffff00;">RDB &amp; AOF 同時開啟，只會載入AOF資料</span>

```INI
# 開啟AOF模式
# 預設 no
appendonly yes
# 檔案名稱
appendfilename "appendonly.aof"
# 目錄名稱(上層預設為啟動目錄)
appenddirname "appendonlydir"
# aof 同步頻率
# appendfsync always|everysec|no
# always:  即時同步(每次寫入同步)
# everysec：每秒同步
# no: 同步時機交給作業系統
# http://antirez.com/post/redis-persistence-demystified.html
appendfsync everysec

# no-appendfsync-on-rewrite 預設no
# 重寫壓縮指令 將多筆設定令重寫成一筆指令
# set A1 1
# set B1 3
# set A1 1 B1 3 => 壓縮重寫
no-appendfsync-on-rewrite yes
# 觸發比例
auto-aof-rewrite-percentage 100
# 觸發檔案大小
auto-aof-rewrite-min-size 64mb

# 如何恢復 ：
# 關閉 redis 將原先appendonly.aof 放回執行目錄下，啟動 redis，會自動載入資料

# aof 檔案損壞
# /usr/local/bin/redis-check-aof --fix appendonly.aof
# 備份該檔
# 重啟服務

```

##### RDB &amp; AOF 使用時機

- 官方推薦 都開
- 資料不嚴謹，可單獨使用RDB
- 不建議單獨使用AOF，可能有BUG
- Redis <span class="s1">只做</span>cache <span class="s1">用，可都不開</span>

---

### <span class="s1">sentinel config</span>

```INI
sentinel monitor mymaster 192.168.1.1 6379 2
sentinel auth-pass mymaster 12345678
```

https://hsiehjenhsuan.medium.com/spring-boot-%E4%BD%BF%E7%94%A8-lettuce-%E8%A8%AD%E5%AE%9A%E5%A4%9A%E5%80%8B-redis-%E9%80%A3%E7%B7%9A-55307dc6a480

# 【Redis】安裝

[https://redis.io/docs/install/install-redis/install-redis-from-source/](https://redis.io/docs/install/install-redis/install-redis-from-source/)

```shell
# download
wget https://download.redis.io/redis-stable.tar.gz

tar -xzvf redis-stable.tar.gz
cd redis-stable
make

sudo make install

# run server 
redis-server
```

# 【Redis相關】RedisSDK

```Java
import org.apache.commons.beanutils.BeanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;

import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/***
 *  @date 2018/9/25 15:07
 * @description:
 */
public class RedisSDK {

    /**
     * 日誌
     */
    public static final Logger LOG = LoggerFactory.getLogger(RedisSDK.class);

    private RedisTemplate<String, Object> redisTemplate;

    private static final String TEMP_STR1 = ",value:";

    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    //=============================common============================

    /**
     * 指定緩存失效時間
     *
     * @param key  鍵
     * @param time 時間(秒)
     * @return
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根據key 獲取過期時間
     *
     * @param key 鍵 不能為null
     * @return 時間(秒) 返回0代表為永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判斷key是否存在
     *
     * @param key 鍵
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 刪除緩存
     *
     * @param key 可以傳一個值 或多個
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null &amp;&amp; key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    //============================操作String=============================

    /**
     * 普通緩存獲取
     *
     * @param key 鍵
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    public String getString(String key) {
        Object result = get(key);
        return result == null ? null : result.toString();
    }


    /**
     * 普通緩存放入
     *
     * @param key   鍵
     * @param value 值
     * @return true成功 false失敗
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

    /**
     * 判斷是否存在
     * @param key
     * @return
     */
    public boolean exists(String key) {
        try {
            return redisTemplate.execute(new RedisCallback<Boolean>() {
                @Override
                public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
                    return redisConnection.exists(key.getBytes());
                }
            });
        } catch (Exception e) {
            LOG.error("", e);
        }
        return false;
    }

    /**
     * 普通緩存放入並設置時間
     *
     * @param key   鍵
     * @param value 值
     * @param time  時間(秒) time要大於0 如果time小於等於0 將設置無限期
     * @return true成功 false 失敗
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 遞增
     *
     * @param key 鍵
     * @param by  要增加幾(大於0)
     * @return
     */
    public long incr(String key, long by) {
        if (by < 0) {
            throw new RuntimeException("遞增因子必須大於0");
        }
        return redisTemplate.opsForValue().increment(key, by);
    }

    /**
     * 遞減
     *
     * @param key 鍵
     * @param by  要減少幾(小於0)
     * @return
     */
    public long decr(String key, long by) {
        if (by < 0) {
            throw new RuntimeException("遞減因子必須大於0");
        }
        return redisTemplate.opsForValue().increment(key, -by);
    }

    //===============================操作Map=================================

    /**
     * HashGet
     *
     * @param key  鍵 不能為null
     * @param item 項 不能為null
     * @return 值
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 獲取hashKey對應的所有鍵值
     *
     * @param key 鍵
     * @return 對應的多個鍵值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     *
     * @param key 鍵
     * @param map 對應多個鍵值
     * @return true 成功 false 失敗
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 並設置時間
     *
     * @param key  鍵
     * @param map  對應多個鍵值
     * @param time 時間(秒)
     * @return true成功 false失敗
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一張hash表中放入數據,如果不存在將創建
     *
     * @param key   鍵
     * @param item  項
     * @param value 值
     * @return true 成功 false失敗
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一張hash表中放入數據,如果不存在將創建
     *
     * @param key   鍵
     * @param item  項
     * @param value 值
     * @param time  時間(秒)  注意:如果已存在的hash表有時間,這裡將會替換原有的時間
     * @return true 成功 false失敗
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 刪除hash表中的值
     *
     * @param key  鍵 不能為null
     * @param item 項 可以使多個 不能為null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判斷hash表中是否有該項的值
     *
     * @param key  鍵 不能為null
     * @param item 項 不能為null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash遞增 如果不存在,就會創建一個 並把新增後的值返回
     *
     * @param key  鍵
     * @param item 項
     * @param by   要增加幾(大於0)
     * @return
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash遞減
     *
     * @param key  鍵
     * @param item 項
     * @param by   要減少記(小於0)
     * @return
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }


    /**
     * @param key
     * @return
     */
    public <T> T hgetAll(String key, Class<T> clazz) {
        try {
            Map<Object, Object> values = hmget(key);
            LOG.info("redis獲取值,key:" + key + TEMP_STR1 + JSONObject.toJSONString(values));
            String json = JSONObject.toJSONString(values);
            Map<String, String> all = (Map) JSONObject.parse(json);
            T t = clazz.newInstance();
            BeanUtils.populate(t, all);
            return t;
        } catch (Exception e) {
            LOG.info("redis獲取值,key:" + key + "失敗");
        }
        return null;
    }

    //============================操作set=============================

    /**
     * 根據key獲取Set中的所有值
     *
     * @param key 鍵
     * @return
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根據value從一個set中查詢,是否存在
     *
     * @param key   鍵
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 將數據放入set緩存
     *
     * @param key    鍵
     * @param values 值 可以是多個
     * @return 成功個數
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 將set數據放入緩存
     *
     * @param key    鍵
     * @param time   時間(秒)
     * @param values 值 可以是多個
     * @return 成功個數
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0) {
                expire(key, time);
            }
            return count == null ? 0L : count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 獲取set緩存的長度
     *
     * @param key 鍵
     * @return
     */
    public long sGetSetSize(String key) {
        try {
            Long size = redisTemplate.opsForSet().size(key);
            return size == null ? 0L : size;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 移除值為value的
     *
     * @param key    鍵
     * @param values 值 可以是多個
     * @return 移除的個數
     */
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count == null ? 0L : count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    //===============================操作list=================================

    /**
     * 獲取list緩存的內容
     *
     * @param key   鍵
     * @param start 開始
     * @param end   結束  0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 獲取list緩存的長度
     *
     * @param key 鍵
     * @return
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 通過索引 獲取list中的值
     *
     * @param key   鍵
     * @param index 索引  index>=0時, 0 表頭,1 第二個元素,依次類推;index<0時,-1,表尾,-2倒數第二個元素,依次類推
     * @return
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 將list放入緩存
     *
     * @param key   鍵
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 將list放入緩存
     *
     * @param key   鍵
     * @param value 值
     * @param time  時間(秒)
     * @return
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 將list放入緩存
     *
     * @param key   鍵
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 將list放入緩存
     *
     * @param key   鍵
     * @param value 值
     * @param time  時間(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根據索引修改list中的某條數據
     *
     * @param key   鍵
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除N個值為value
     *
     * @param key   鍵
     * @param count 移除多少個
     * @param value 值
     * @return 移除的個數
     */
    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 發佈消息
     *
     * @param channel
     * @param message
     */
    public void sendMessage(String channel, Serializable message) {
        redisTemplate.convertAndSend(channel, message);
    }


}
```

# DB 欄位命名與比較

資料庫欄位命名的選擇對於系統的可讀性、一致性和維護性有著重要的影響。不同的資料庫可能有不同的命名慣例，以下是常見的命名方式及其在各種資料庫中的應用。

以下是常見命名方式與對應資料庫的整理表格：

<table id="bkmrk-%E5%91%BD%E5%90%8D%E6%96%B9%E5%BC%8F-%E5%91%BD%E5%90%8D%E7%89%B9%E5%BE%B5-%E6%87%89%E7%94%A8%E8%B3%87%E6%96%99%E5%BA%AB-%E9%A7%9D%E5%B3%B0%E5%BC%8F%E5%91%BD"><thead><tr><th>**命名方式**</th><th>**命名特徵**</th><th>**應用資料庫**</th></tr></thead><tbody><tr><td>**駝峰式命名法**</td><td>單字首字母小寫，後續單字首字母大寫，如：`firstName`</td><td>**MongoDB**、**Cassandra**：與程式碼結合較緊密的 NoSQL 資料庫常使用</td></tr><tr><td>**蛇形命名法**</td><td>單字之間用底線 `_` 分隔，全小寫，如：`first_name`</td><td>**PostgreSQL**、**MySQL**、**Oracle**、**SQLite**：傳統 SQL 資料庫中最常見</td></tr><tr><td>**帕斯卡命名法**</td><td>每個單字首字母大寫，沒有分隔符號，如：`FirstName`</td><td>**Microsoft SQL Server**、**Entity Framework**：與程式碼結合時偶爾使用</td></tr><tr><td>**中線命名法**</td><td>單字之間用連字符 `-` 分隔，全小寫，如：`first-name`</td><td>**Elasticsearch**：主要用於索引和類型，但不常用於欄位</td></tr><tr><td>**全小寫命名法**</td><td>全部字母小寫，無分隔符號，如：`firstname`</td><td>**MySQL**、**SQLite**：為了避免大小寫區分問題，常用於輕量或無區分大小寫的系統</td></tr><tr><td>**前綴或後綴命名法**</td><td>欄位名稱加上描述性前綴或後綴，如：`user_id`、`created_at`</td><td>**PostgreSQL**、**MySQL**、**SQL Server**、**MongoDB**：常用於表示主鍵、外鍵或時間戳等特定欄位</td></tr></tbody></table>

此表格總結了各種命名方式的特徵和常用的資料庫，選擇命名方式時，應根據資料庫特性和開發需求來做決定，並保持專案中的一致性。

### 常見欄位命名方式及應用

#### 1. **駝峰式命名法（Camel Case）**

- **命名法**：單字首字母小寫，接下來每個單字的首字母大寫。
- **範例**：`firstName`、`lastName`、`orderDate`
- **應用資料庫**： 
    - **MongoDB**：作為 NoSQL 資料庫，MongoDB 使用 JSON/BSON 結構，開發者常使用駝峰式命名與程式碼一致。
    - **Cassandra**：由於 Cassandra 支援靈活的資料模式，駝峰式命名法也被頻繁採用，尤其在與程式碼整合時。

#### 2. **蛇形命名法（Snake Case）**

- **命名法**：單字之間用底線 `_` 分隔，所有字母小寫。
- **範例**：`first_name`、`last_name`、`order_date`
- **應用資料庫**： 
    - **PostgreSQL**：最常見的命名方式，與 SQL 語法兼容性好，避免大小寫問題。
    - **MySQL**：由於 MySQL 預設不區分大小寫，蛇形命名法常被使用以保持命名的一致性和可讀性。
    - **Oracle**：儘管 Oracle 支援大小寫區分，蛇形命名法依然流行。
    - **SQLite**：輕量級資料庫中也常見此命名方式，方便管理。

#### 3. **帕斯卡命名法（Pascal Case）**

- **命名法**：每個單字首字母大寫，沒有分隔符號。
- **範例**：`FirstName`、`LastName`、`OrderDate`
- **應用資料庫**： 
    - **Microsoft SQL Server**：部分開發者，特別是使用 Microsoft 技術堆疊的，會選擇帕斯卡命名法以與程式命名風格保持一致。
    - **Entity Framework**：當與 SQL Server 或其他資料庫結合時，帕斯卡命名法常用於映射資料庫欄位和程式屬性名稱。

#### 4. **中線命名法（Kebab Case）**

- **命名法**：單字之間用連字符 `-` 分隔，所有字母小寫。
- **範例**：`first-name`、`last-name`、`order-date`
- **應用資料庫**： 
    - **Elasticsearch**：儘管大部分資料庫不支援欄位中使用連字符，中線命名偶爾會出現在 Elasticsearch 的索引或類型中，但在欄位名稱中較少見。

#### 5. **全小寫命名法**

- **命名法**：所有字母均小寫，無任何分隔符號。
- **範例**：`firstname`、`lastname`、`orderdate`
- **應用資料庫**： 
    - **MySQL**：由於 MySQL 不區分大小寫，許多開發者會選擇全小寫命名來避免混淆。
    - **SQLite**：小型應用中，開發者常使用全小寫命名來簡化欄位管理。

#### 6. **前綴或後綴命名法**

- **命名法**：在欄位名稱前或後添加描述性前綴或後綴。
- **範例**：`user_id`、`created_at`
- **應用資料庫**： 
    - **PostgreSQL**、**MySQL**、**SQL Server**：此命名法常見於資料表中，特別是主鍵、外鍵或時間戳等欄位，如 `id`、`created_at`。
    - **MongoDB**：在 NoSQL 環境中，這種命名法也被用來標記欄位的特定用途，便於區分不同類型的資料。

### 小結

- **SQL 資料庫（PostgreSQL、MySQL、SQL Server、Oracle）**：推薦使用 **蛇形命名法**，這是一種最常見的標準化命名方式，能在 SQL 查詢中避免大小寫問題，並提高可讀性。
- **NoSQL 資料庫（MongoDB、Cassandra、Elasticsearch）**：**駝峰式命名法** 和 **全小寫命名法** 較為常見，這些資料庫的靈活數據模型和與 JSON 的緊密結合，使得駝峰式命名更符合程式開發習慣。

選擇合適的命名方式應根據具體資料庫、開發團隊的需求和維護便利性來決定，並保持整個專案中的一致性。

# 【Redis】Sentinel 相關指令

下面整理一份「常用 Redis Sentinel 指令速查」（以你目前 master name = `mymaster` 為例）。  
先記得：Sentinel 是連 **26379**（或你映射的 26380/26381），而且很多命令需要先 AUTH（若你 Sentinel 有開帳密）。

---

## 連線與基本檢查

```bash
redis-cli -h <sentinel_ip> -p 26379
redis-cli -h <sentinel-host> -p 26379 --user <sentinel-user> -a <sentinel-pass>

```

- 測連線

```redis
PING

10.10.10.202:26379> ping
PONG
```

- 目前登入的 ACL user（若開 ACL）

```redis
ACL WHOAMI

```

- 授權（若需要）

```redis
AUTH <user> <password>
# 或舊式
AUTH <password>

```

---

## 查 master / topology（最常用）

- 查所有被監控的 masters（摘要列表）

```redis
SENTINEL masters

1)  1) "name"
    2) "mymaster"
    3) "ip"
    4) "10.10.10.202"
    5) "port"
    6) "6379"
    7) "runid"
    8) "3bf7dbeca2952f3b9bbe3961b2b75bc3c03bc040"
    9) "flags"
   10) "master"
   11) "link-pending-commands"
   12) "0"
   13) "link-refcount"
   14) "1"
   15) "last-ping-sent"
   16) "0"
   17) "last-ok-ping-reply"
   18) "533"
   19) "last-ping-reply"
   20) "533"
   21) "down-after-milliseconds"
   22) "30000"
   23) "info-refresh"
   24) "7634"
   25) "role-reported"
   26) "master"
   27) "role-reported-time"
   28) "44657033"
   29) "config-epoch"
   30) "9"
   31) "num-slaves"
   32) "2"
   33) "num-other-sentinels"
   34) "2"
   35) "quorum"
   36) "2"
   37) "failover-timeout"
   38) "180000"
   39) "parallel-syncs"
   40) "1"

```

- 查特定 master 的詳細資訊

```redis
SENTINEL master mymaster

```

- 取得 master 的位址（最常用，拿 IP/Port）

```redis
SENTINEL get-master-addr-by-name mymaster

```

- 查 master 的 replicas（從節點清單）

```redis
SENTINEL replicas mymaster
# 舊名（相容）
SENTINEL slaves mymaster

```

- 查同一個 master 的其他 sentinels

```redis
SENTINEL sentinels mymaster

```

---

## 健康/狀態判斷（排錯必備）

- 看 master 是否被標記 down、目前狀態旗標

```redis
SENTINEL master mymaster
# 觀察 "flags"、"last-ok-ping-reply"、"s-down-time" 等欄位

```

- Sentinel 自己資訊（版本、runid、監控數量）

```redis
INFO

```

- 看 Sentinel 的事件（需要開啟通知才會比較多）

```redis
SENTINEL ckquorum mymaster

```

> 這個會告訴你 quorum 是否足夠（例如你設定 quorum=2，是否能形成共識）。

---

## 故障轉移操作（小心用）

- 立即對指定 master 觸發 failover

```redis
SENTINEL failover mymaster

```

- 中止正在進行的 failover（如果卡住）

```redis
SENTINEL abortfailover mymaster

```

- 主動要求 Sentinel 重新掃描、更新配置（常用在手動改 IP/Port 後）

```redis
SENTINEL reset mymaster
# 或 reset 全部（小心）
SENTINEL reset *

```

---

## 動態改監控設定（線上調參）

- 修改 down-after-milliseconds（多久沒回就判定 down）

```redis
SENTINEL set mymaster down-after-milliseconds 30000

```

- 修改 failover-timeout（failover 超時）

```redis
SENTINEL set mymaster failover-timeout 180000

```

- 修改 parallel-syncs（一次同步幾台 replica）

```redis
SENTINEL set mymaster parallel-syncs 1

```

- 修改 quorum（通常不建議頻繁改）

```redis
SENTINEL set mymaster quorum 2

```

- 查看目前可用的 set 參數（不同版本可能略不同）

```redis
SENTINEL help

```

---

## 通知與腳本（你目前有 deny）

你現在設定：

```conf
sentinel deny-scripts-reconfig yes

```

所以 sentinel 腳本重配置會被限制。常見指令/配置（了解用）：

- 設定通知腳本（在 config 檔內）

```conf
sentinel notification-script mymaster /path/to/script
sentinel client-reconfig-script mymaster /path/to/script

```

---

## 常用排錯流程（超快）

1. Sentinel 能不能連：

```redis
PING

```

2. master 位址：

```redis
SENTINEL get-master-addr-by-name mymaster

```

3. master 狀態：

```redis
SENTINEL master mymaster

```

4. replicas 狀態：

```redis
SENTINEL replicas mymaster

```

5. quorum 是否健康：

```redis
SENTINEL ckquorum mymaster

```

---

## 小提醒（你很常遇到的）

- **Sentinel 不支援 `SELECT`**，因為它不是資料庫服務，是監控/仲裁服務。
- `SENTINEL get-master-addr-by-name` 回的 IP/HostName 對外能不能用，取決於你的網路（Docker 內外網、DNS、announce 等設定）。

# Docker

# 【上課筆記】【hiskio】Docker 完全實作！結合 3 大語言掌握容器實務應用

##### 上課講義

[Docker 完全實作 – Dropbox Paper](https://paper.dropbox.com/doc/Docker--BaNWxS3HDfry~X6QJb7Ze4plAg-pcGbQoyLtmiPPoIlkkK2r)

---

[![image-1642476831997.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/scaled-1680-/0giARnkNtAdZy4q4-image-1642476831997.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/0giARnkNtAdZy4q4-image-1642476831997.png)

nocopy : 存在檔案不覆蓋

# <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">Docker 完全實作</span>

# <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">![man teacher](https://paper.dropboxstatic.com/static/img/ace/emoji/1f468-1f3eb.png?version=6.0.0 "man teacher")</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> 讓我們先複習一下 Docker 的安裝</span>

## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">Windows 上的 Docker 環境建置</span>

<div id="bkmrk-https%3A%2F%2Fdocs.docker."><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://docs.docker.com/desktop/windows/install/","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://docs.docker.com/desktop/windows/install/">[https://docs.docker.com/desktop/windows/install/](https://docs.docker.com/desktop/windows/install/)  
</span></span></div><div id="bkmrk-%E4%BB%80%E9%BA%BC%E6%98%AFwsl%EF%BC%9F">- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">什麼是WSL？</span>

</div><div id="bkmrk-%E9%81%A9%E7%94%A8%E6%96%BC-linux-%E7%9A%84-windows-">- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">適用於 Linux 的 Windows 子系統可讓開發人員執行 GNU/Linux 環境</span> <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z h-lparen">(包括大部分的命令列工具、公用程式和應用程式)，直接在</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> Windows 上執行，不需進行修改，不會造成傳統虛擬機器或 dualboot 設定的額外負荷。 </span>

</div><div id="bkmrk-https%3A%2F%2Fdocs.microso">- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z url">[https://docs.microsoft.com/zh-tw/windows/wsl/about](https://docs.microsoft.com/zh-tw/windows/wsl/about)</span>

</div><div id="bkmrk--0"></div><div id="bkmrk-https%3A%2F%2Fwww.youtube."><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"youtube","url":"https://www.youtube.com/watch?v=lIkxbE_We1I&","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://www.youtube.com/watch?v=lIkxbE_We1I&">[https://www.youtube.com/watch?v=lIkxbE\_We1I&amp;](https://www.youtube.com/watch?v=lIkxbE_We1I&)  
</span></span></div><div id="bkmrk-https%3A%2F%2Fyoutu.be%2Flik"><span class="attrlink url author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z url">[https://youtu.be/lIkxbE\_We1I](https://youtu.be/lIkxbE_We1I)</span></div>## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">Mac 上的 Docker 環境建置</span>

<div id="bkmrk-https%3A%2F%2Fdocs.docker.-0"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://docs.docker.com/desktop/mac/install/","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://docs.docker.com/desktop/mac/install/">[https://docs.docker.com/desktop/mac/install/](https://docs.docker.com/desktop/mac/install/)  
</span></span></div><div id="bkmrk-%E5%A6%82%E6%9E%9C%E6%82%A8%E7%9A%84%E7%AD%86%E9%9B%BB%E6%98%AFapple-m1-%28sil">- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">如果您的筆電是Apple M1</span> <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z h-lparen">(Silicon)</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> CPU的話，請參考此處的安裝方式：</span>

</div><div id="bkmrk-https%3A%2F%2Fdocs.docker.-1">- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z url">[https://docs.docker.com/desktop/mac/apple-silicon/](https://docs.docker.com/desktop/mac/apple-silicon/)</span>

</div><div id="bkmrk--1"></div><div id="bkmrk-apple-m1%E7%9A%84%E9%99%90%E5%88%B6"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">![police car light](https://paper.dropboxstatic.com/static/img/ace/emoji/1f6a8.png?version=6.0.0 "police car light")</span> <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**Apple M1的限制**</span></div>- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">非所有映像檔都可用於 </span><span class="inline-code author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">ARM64</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> 架構。可以使用</span> <span class="inline-code author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">--platform linux/amd64</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> 模擬運行。但 </span><span class="inline-code author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">mysql</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> 不適用於 ARM64，不過可以使用 </span><span class="inline-code author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">mariadb</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> 解決此問題。</span>
- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">模擬Intel的容器可能會有不穩定的狀況出現，除了速度比較慢之外，也會使用更多的記憶體。</span>
- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">無法從容器內部 </span><span class="inline-code author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">ping</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> 到 Internet。要測試網絡，我們建議使用 </span><span class="inline-code author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">curl</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> 或 </span><span class="inline-code author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">wget</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">。</span>
- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">當 </span><span class="inline-code author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">TCP half-close</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">時，數據可能偶爾會丟失。</span>

<div id="bkmrk-%C2%A0">- 

</div>## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">Linux 上的 Docker 環境建置</span>

<div id="bkmrk-https%3A%2F%2Fdocs.docker.-2"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://docs.docker.com/engine/install/ubuntu/","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://docs.docker.com/engine/install/ubuntu/">[https://docs.docker.com/engine/install/ubuntu/](https://docs.docker.com/engine/install/ubuntu/)  
</span></span></div>## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">實務上需要注意的事</span>

<div id="bkmrk--2"></div><div id="bkmrk-%E5%9C%A8%E5%A4%A7%E5%9E%8B%E7%B5%84%E7%B9%94%EF%BC%88%E8%B6%85%E9%81%8E-250-%E5%90%8D%E5%93%A1%E5%B7%A5%E6%88%96%E8%B6%85%E9%81%8E-">- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">![light bulb](https://paper.dropboxstatic.com/static/img/ace/emoji/1f4a1.png?version=6.0.0 "light bulb")</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> 在大型組織（超過 250 名員工或超過 1000 萬美元的收入）中使用 Docker Desktop 需要付費訂閱 Docker。 雖然此條款的生效日期是 2021 年 8 月 31 日，但對於需要付費訂閱的用戶，寬限期到 2022 年 1 月 31 日。</span>

</div><div id="bkmrk--3"></div><div id="bkmrk-cpu%2Fmemory%E8%B3%87%E6%BA%90"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**CPU/Memory資源**</span></span></div><div id="bkmrk--5"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"image","url":"https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634205117720_image.png","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634205117720_image.png">![](https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634205117720_image.png)</span></span></div><div id="bkmrk-%E6%8E%9B%E8%BC%89%E7%9B%AE%E9%8C%84"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**掛載目錄**</span></span></div><div id="bkmrk--6"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"image","url":"https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634205302746_image.png","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634205302746_image.png">![](https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634205302746_image.png)</span></span></div><div id="bkmrk-buildkit"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**Buildkit**</span></span></div><div id="bkmrk--7"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"image","url":"https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634205247210_image.png","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634205247210_image.png">![](https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634205247210_image.png)</span></span></div><div id="bkmrk-%E5%9B%9E%E5%BE%A9%E5%88%9D%E5%A7%8B%E8%A8%AD%E5%AE%9A"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**回復初始設定**</span></span></div><div id="bkmrk--8"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"image","url":"https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634207204490_image.png","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634207204490_image.png">![](https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634207204490_image.png)</span></span></div># <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">![whale](https://paper.dropboxstatic.com/static/img/ace/emoji/1f40b.png?version=6.0.0 "whale")</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> 工欲善其事，必先利其器：Docker 技術快覽</span>

## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">Docker 常用指令 </span>

<div id="bkmrk--9"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"image","url":"https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634184121714_image.png","thread-757518322229675521325990":"false","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"Source: https://docs.google.com/presentation/d/1pBBTzAbKAYTDXDE6ZDZzQkFKzFisMPQB8T96o1-N7po/pub?start=false&loop=false&delayms=3000&slide=id.g19613dd9d0_0_541\n","attribs":"*0+4e|1+1"},"pool":{"numToAttrib":{"0":["author","d.iX86Va0dCNNJDjPG9iZa3ls0ROswnpuOedberDa3xTLqmSmT1K"]},"attribToNum":{"author,d.iX86Va0dCNNJDjPG9iZa3ls0ROswnpuOedberDa3xTLqmSmT1K":0},"nextNum":1,"dirty":true,"firstDirtyNum":0}}}}]}" data-ace-gallery-urls="https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634184121714_image.png">![](https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634184121714_image.png)</span></span></div><div id="bkmrk-%E5%9F%B7%E8%A1%8C%E5%AE%B9%E5%99%A8"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**執行容器**</span></span></div><div id="bkmrk-%23-%E8%B7%91%E4%B8%80%E5%80%8B%E6%96%B0%E7%9A%84apache%E5%AE%B9%E5%99%A8">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># 跑一個新的apache容器</span>`</div><div id="bkmrk-docker-run-httpd%3Aalp">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker run httpd:alpine</span>`</div><div id="bkmrk--10"></div><div id="bkmrk-%23-%E8%B7%91%E4%B8%80%E5%80%8B%E8%83%8C%E6%99%AF%E5%9F%B7%E8%A1%8C%E7%9A%84apache%E5%AE%B9%E5%99%A8%EF%BC%8C%E4%B8%A6">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># 跑一個背景執行的apache容器，並取名為apache</span>`</div><div id="bkmrk-docker-run--d---name">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker run -d --name apache httpd:alpine </span>`</div><div id="bkmrk--11"></div><div id="bkmrk-%23-%E8%B7%91%E4%B8%80%E5%80%8Bredis%E5%AE%B9%E5%99%A8%EF%BC%8C%E5%95%9F%E5%8B%95%E8%99%9B%E6%93%AC%E7%B5%82%E7%AB%AF%E6%A9%9F">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># 跑一個redis容器，啟動虛擬終端機，進入互動模式，在容器中執行sh</span>`</div><div id="bkmrk-docker-run--it---rm-">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker run -it --rm redis:alpine sh</span>`</div>- <span class="inline-code author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">-it</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> : </span><span class="inline-code author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">i</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">指的就是互動模式，而 </span><span class="inline-code author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">t</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">就是指terminal的意示</span>
- <span class="inline-code author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">--rm</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> :指的是當容器結束後，將容器從系統移除</span>

<div id="bkmrk--12"></div><div id="bkmrk-%E5%88%97%E5%87%BA%E5%AE%B9%E5%99%A8"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**列出容器**</span></span></div><div id="bkmrk-%23-%E5%88%97%E5%87%BA%E6%89%80%E6%9C%89%E5%9F%B7%E8%A1%8C%E4%B8%AD%E5%AE%B9%E5%99%A8">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><em># 列出所有執行中容器</em></span>`</div><div id="bkmrk-docker-ps">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker ps</span>`</div><div id="bkmrk--13"></div><div id="bkmrk-%23-%E5%88%97%E5%87%BA%E6%89%80%E6%9C%89%E5%AE%B9%E5%99%A8">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><em># 列出所有容器</em></span>`</div><div id="bkmrk-docker-ps--a">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker ps -a</span>`</div><div id="bkmrk--14"></div><div id="bkmrk-%E5%81%9C%E6%AD%A2%E5%AE%B9%E5%99%A8"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**停止容器**</span></span></div><div id="bkmrk-%23-%E5%81%9C%E6%AD%A2apache-%E5%AE%B9%E5%99%A8">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># 停止apache 容器</span>`</div><div id="bkmrk-docker-stop-apache">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker stop </span><span class="thread-988202269380550252733434 attrcomment attrcommentfirst thread-988202269380550252733434-first author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="comment-extra-inner-span">apache</span></span>`</div><div id="bkmrk--15"></div><div id="bkmrk-%E9%87%8D%E6%96%B0%E5%95%9F%E5%8B%95%E5%AE%B9%E5%99%A8"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**重新啟動容器**</span></span></div><div id="bkmrk-%23-%E9%87%8D%E6%96%B0%E5%95%9F%E5%8B%95apache">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># 重新啟動apache</span>`</div><div id="bkmrk-docker-restart-apach">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker restart </span><span class="thread-988202269380550252733434 attrcomment attrcommentfirst thread-988202269380550252733434-first author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="comment-extra-inner-span">apache</span></span>`</div><div id="bkmrk--16"></div><div id="bkmrk-kill%E5%AE%B9%E5%99%A8"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**Kill容器**</span></span></div><div id="bkmrk-%23%E5%BC%B7%E5%88%B6%E5%81%9C%E6%AD%A2%E5%AE%B9%E5%99%A8">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#強制停止容器</span>`</div><div id="bkmrk-docker-kill-apache">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker kill apache</span>`</div><div id="bkmrk--17"></div><div id="bkmrk-%E7%A7%BB%E9%99%A4%E5%AE%B9%E5%99%A8"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**移除容器**</span></span></div><div id="bkmrk-%23%E7%A7%BB%E9%99%A4apache">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#移除apache</span>`</div><div id="bkmrk-docker-rm-apache">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker rm apache</span>`</div><div id="bkmrk-%23%E6%AA%A2%E6%9F%A5%E6%89%80%E6%9C%89%E5%AE%B9%E5%99%A8">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#檢查所有容器</span>`</div><div id="bkmrk-docker-ps--a-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker ps -a</span>`</div><div id="bkmrk-%23%E7%A7%BB%E9%99%A4%E6%89%80%E6%9C%89%E5%AE%B9%E5%99%A8">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#移除所有容器</span>`</div><div id="bkmrk-docker-rm--f-%24%28docke">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker rm -f $(docker ps -a -q)</span>`</div><div id="bkmrk--18"></div><div id="bkmrk-%E8%A4%87%E8%A3%BD%E6%AA%94%E6%A1%88"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**複製檔案**</span></span></div><div id="bkmrk-%23-%E5%BB%BA%E7%AB%8B%E4%B8%80%E5%80%8Bhello.html">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># 建立一個hello.html</span>`</div><div id="bkmrk-echo-%22hello%22-%3E-hello">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">echo "hello" > hello.html</span>`</div><div id="bkmrk-%23-%E5%BB%BA%E7%AB%8Bapache">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># 建立apache</span>`</div><div id="bkmrk-docker-run--d---name-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker run -d --name apache httpd:alpine </span>`</div><div id="bkmrk-%23-%E8%A4%87%E8%A3%BD%E6%AA%94%E6%A1%88%E8%87%B3apache-%E5%AE%B9%E5%99%A8%E4%B8%AD">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># 複製檔案至apache 容器中</span>`</div><div id="bkmrk-docker-cp-hello.html">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker cp hello.html apache:/usr/local/apache2/htdocs/</span>`</div><div id="bkmrk-%23-%E8%A4%87%E8%A3%BDapache-%E5%AE%B9%E5%99%A8%E4%B8%AD%E7%9A%84%E6%AA%94%E6%A1%88%E8%87%B3%E6%9C%AC%E6%A9%9F">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># 複製apache 容器中的檔案至本機</span>`</div><div id="bkmrk-docker-cp-apache%3A%2Fus">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker cp apache:/usr/local/apache2/htdocs/index.html ./index.html</span>`</div><div id="bkmrk--19"></div><div id="bkmrk-%E5%9C%A8%E5%AE%B9%E5%99%A8%E5%85%A7%E5%9F%B7%E8%A1%8C%E7%A8%8B%E5%BC%8F"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**在容器內執行程式**</span></span></div><div id="bkmrk-%23%E5%9C%A8%E5%AE%B9%E5%99%A8%E4%B8%AD%E5%9F%B7%E8%A1%8Cshell">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#在容器中執行shell</span>`</div><div id="bkmrk-docker-exec--it-apac">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker exec -it apache sh</span>`</div><div id="bkmrk--20"></div><div id="bkmrk-%E6%8E%9B%E8%BC%89%E7%9B%AE%E9%8C%84-0"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**掛載目錄**</span></span></div><div id="bkmrk-%23%E6%8A%8Ahello.html%E6%8E%9B%E6%88%90index.">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#把hello.html掛成index.html</span>`</div><div id="bkmrk-docker-run--d---name-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker run -d --name apache2 -v $PWD/hello.html:/usr/local/apache2/htdocs/index.html httpd:alpine</span>`</div><div id="bkmrk-%23-copy%E5%87%BA%E4%BE%86%E9%A9%97%E8%AD%89%E5%85%A7%E5%AE%B9%E6%98%AF%E5%90%A6%E4%B8%80%E6%A8%A3">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># copy出來驗證內容是否一樣</span>`</div><div id="bkmrk-docker-cp-apache2%3A%2Fu">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker cp apache2:/usr/local/apache2/htdocs/index.html ./index.html</span>`</div><div id="bkmrk-diff-index.html-hell">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">diff index.html hello.html</span>`</div><div id="bkmrk--21"></div><div id="bkmrk-%E5%BB%BA%E7%AB%8B%E5%A4%96%E9%83%A8%E9%80%A3%E7%B7%9A"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**建立外部連線**</span></span></div><div id="bkmrk-%23%E6%8A%8A%E3%80%8C%E6%9C%AC%E6%A9%9F%E7%9A%8480%E5%9F%A0%E3%80%8D%E5%B0%8D%E6%87%89%E5%88%B0%E3%80%8C%E5%AE%B9%E5%99%A8%E4%B8%AD%E7%9A%8480">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#把「本機的80埠」對應到「容器中的80埠」</span>`</div><div id="bkmrk-docker-run--d--v-%24pw">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker run -d -v $PWD/hello.html:/usr/local/apache2/htdocs/index.html -p 80:80 httpd:alpine</span>`</div><div id="bkmrk-http%3A%2F%2F127.0.0.1%3A80"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z url">[http://127.0.0.1:80](http://127.0.0.1:80)</span></div><div id="bkmrk--22"></div><div id="bkmrk-%E5%A6%82%E4%BD%95%E5%BE%9E%E5%AE%B9%E5%99%A8%E4%B8%AD%EF%BC%8C%E9%80%A3%E5%88%B0%E6%9C%AC%E6%A9%9F%E4%B8%8A%E7%9A%84%E6%9C%8D%E5%8B%99%E5%91%A2%EF%BC%9F">- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">![light bulb](https://paper.dropboxstatic.com/static/img/ace/emoji/1f4a1.png?version=6.0.0 "light bulb")</span> <span class="attrlink url author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">[如何從容器中，連到本機上的服務呢](https://stackoverflow.com/questions/24319662/from-inside-of-a-docker-container-how-do-i-connect-to-the-localhost-of-the-mach)</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">？</span>

</div><div id="bkmrk-%E5%8F%AF%E4%BB%A5%E5%9C%A8%E5%AE%B9%E5%99%A8%E4%B8%AD%E4%BD%BF%E7%94%A8-host.docker">- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">可以在容器中使用 </span><span class="inline-code author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">host.docker.internal</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> 來連接本機上的服務喔！</span>

</div><div id="bkmrk--23"></div><div id="bkmrk--24"></div><div id="bkmrk-%E6%9F%A5%E7%9C%8B%E5%AE%B9%E5%99%A8%E7%8B%80%E6%85%8B"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**查看容器狀態**</span></span></div><div id="bkmrk-%23-%E6%9F%A5%E7%9C%8B-cpu%E3%80%81%E8%A8%98%E6%86%B6%E9%AB%94%E8%88%87%E7%B6%B2%E8%B7%AF%E7%94%A8%E9%87%8F">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><em># 查看 CPU、記憶體與網路用量</em></span>`</div><div id="bkmrk-docker-stats">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker stats</span>`</div><div id="bkmrk--25"></div><div id="bkmrk-%E6%9F%A5%E7%9C%8B%E5%AE%B9%E5%99%A8%E5%85%A7%E7%9A%84%E8%A1%8C%E7%A8%8B"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**查看容器內的行程**</span></span></div><div id="bkmrk-%23-%E6%9F%A5%E7%9C%8B%E5%AE%B9%E5%99%A8%E5%85%A7%E9%83%A8%E7%9A%84%E8%A1%8C%E7%A8%8B">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><em># 查看容器內部的行程</em></span>`</div><div id="bkmrk-docker-top-apache">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker top apache</span>`</div><div id="bkmrk-docker-top-apache2">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker top apache2</span>`</div><div id="bkmrk--26"></div><div id="bkmrk-%E6%9F%A5%E7%9C%8B%E5%AE%B9%E5%99%A8logs"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**查看容器Logs**</span></span></div><div id="bkmrk-%23%E6%9F%A5%E7%9C%8Blog">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#查看log</span>`</div><div id="bkmrk-docker-logs-apache">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker logs apache</span>`</div><div id="bkmrk-%23%E6%8C%81%E7%BA%8C%E6%9F%A5%E7%9C%8B">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#持續查看</span>`</div><div id="bkmrk-docker-logs--f-apach">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker logs -f apache2</span>`</div><div id="bkmrk--27"></div><div id="bkmrk-%E5%BB%BA%E7%AB%8B%E6%98%A0%E5%83%8F%E6%AA%94"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**建立映像檔**</span></span></div><div id="bkmrk-%23-%E6%96%BC%E7%95%B6%E5%89%8D%E7%9B%AE%E9%8C%84%EF%BC%8C%E6%8C%89dockerfile.">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># 於當前目錄，按Dockerfile.test中的指令，建立test:v1的映像檔</span>`</div><div id="bkmrk-docker-build-.--f-do">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker build . -f Dockerfile.test -t test:v1</span>`</div><div id="bkmrk--28"></div><div id="bkmrk-%E6%8B%89%E5%8F%96%E8%88%87%E6%8E%A8%E9%80%81%E6%98%A0%E5%83%8F%E6%AA%94"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**拉取與推送映像檔**</span></span></div><div id="bkmrk-%23%E5%BE%9Edocker-hub%E4%B8%8B%E8%BC%89-node%E7%9A%84">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#從docker hub下載 node的image</span>`</div><div id="bkmrk-docker-pull-node%3Aalp">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker pull node:alpine</span>`</div><div id="bkmrk-%23%E5%B0%87%E4%B8%8B%E4%BE%86%E7%9A%84image%EF%BC%8C%E5%86%8D%E7%B5%A6%E6%96%BC%E6%96%B0%E7%9A%84%E6%A8%99%E7%B1%A4">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#將下來的image，再給於新的標籤</span>`</div><div id="bkmrk-docker-tag-node%3Aalpi">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker tag node:alpine macchiang/node:alpine</span>`</div><div id="bkmrk-%23%E6%8E%A8%E9%80%81%E6%96%B0%E7%9A%84%E6%A8%99%E7%B1%A4%E8%87%B3%E7%A7%81%E4%BA%BA%E5%80%89%E5%BA%AB">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#推送新的標籤至私人倉庫</span>`</div><div id="bkmrk-docker-push-macchian">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker push macchiang/node:alpine</span>`</div><div id="bkmrk--29"></div><div id="bkmrk-%E6%B8%85%E9%99%A4%E6%98%A0%E5%83%8F%E6%AA%94%E8%88%87%E5%AE%B9%E5%99%A8"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**清除映像檔與容器**</span></span></div><div id="bkmrk-%23%E5%81%9C%E6%8E%89%E5%85%A8%E9%83%A8container">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#停掉全部container</span>`</div><div id="bkmrk-docker-stop-%24%28docker">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker stop $(docker ps -a -q)</span>`</div><div id="bkmrk-%23%E5%88%AA%E9%99%A4%E6%89%80%E6%9C%89%E6%B2%92%E6%9C%89%E4%BD%BF%E7%94%A8%E7%9A%84container%E8%88%87">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#刪除所有沒有使用的container與image</span>`</div><div id="bkmrk-docker-system-prune">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker system prune</span>`</div><div id="bkmrk-%23%E5%88%AA%E9%99%A4%E6%89%80%E6%9C%89%E6%98%A0%E5%83%8F%E6%AA%94">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#刪除所有映像檔</span>`</div><div id="bkmrk-docker-rmi-%24%28docker-">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker rmi $(docker images -a -q)</span>`</div><div id="bkmrk-%23%E6%B8%85%E9%99%A4volume">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#清除volume</span>`</div><div id="bkmrk-docker-volume-rm-%24%28d">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker volume rm $(docker volume ls)</span>`</div><div id="bkmrk--30"></div><div id="bkmrk-%23%E5%88%AA%E9%99%A4%E6%89%80%E6%9C%89%E6%B2%92%E6%9C%89%E4%BD%BF%E7%94%A8%E7%9A%84container%E3%80%81">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#刪除所有沒有使用的container、image及所有volume</span>`</div><div id="bkmrk-docker-system-prune-">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker system prune -a -f --volumes</span>`</div><div id="bkmrk--31"></div>## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">Dockerfile 起手式</span>

<div id="bkmrk-%E5%9F%BA%E6%9C%AC%E7%B5%84%E6%88%90"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**基本組成**</span></span></div><div id="bkmrk-%23-base-image---offic">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><em># Base Image - Official Alpine</em></span>`</div><div id="bkmrk-from-alpine">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>FROM</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> alpine</span>`</div><div id="bkmrk-%C2%A0-0">` `</div><div id="bkmrk-label-%22maintainer%22%3D%22">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>LABEL</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> "maintainer"="macchiang@gmail.com"</span>`</div><div id="bkmrk-%C2%A0-1">` `</div><div id="bkmrk-%23-upgrade-existing-p">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><em># Upgrade existing packages in the base image</em></span>`</div><div id="bkmrk-run-apk---no-cache-u">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>RUN</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> apk </span><span class="highlight author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z" data-highlight-color="#FFBFB5">--no-cache</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> upgrade</span>`</div><div id="bkmrk-%C2%A0-2">` `</div><div id="bkmrk-%23-install-apache-fro">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><em># Install apache from packages with out caching install files</em></span>`</div><div id="bkmrk-run-apk-add---no-cac">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>RUN</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> apk add --no-cache apache2</span>`</div><div id="bkmrk-%C2%A0-3">` `</div><div id="bkmrk-%23-open-port-for-http">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><em># Open port for httpd access</em></span>`</div><div id="bkmrk-expose-80">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>EXPOSE</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> 80</span>`</div><div id="bkmrk-%C2%A0-6">` `</div><div id="bkmrk-%23-run-httpd-in-foreg">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><em># Run httpd in foreground</em></span>`</div><div id="bkmrk-cmd-%5B%22-d%22%2C%22foregroun">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>CMD</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> ["-D","FOREGROUND"]</span>`</div><div id="bkmrk-%C2%A0-7">` `</div><div id="bkmrk-%23-start-httpd-when-c">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><em># Start httpd when container runs</em></span>`</div><div id="bkmrk-entrypoint-%5B%22%2Fusr%2Fsb">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>ENTRYPOINT</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> ["/usr/sbin/httpd"]</span>`</div><div id="bkmrk--32"></div><div id="bkmrk-%C2%A0cmd-vs.-entrypoint">- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">![light bulb](https://paper.dropboxstatic.com/static/img/ace/emoji/1f4a1.png?version=6.0.0 "light bulb")</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> </span><span class="inline-code author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**CMD**</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> vs. </span><span class="inline-code author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**ENTRYPOINT**</span>

</div><div id="bkmrk-cmd%E9%85%8D%E7%BD%AE%E7%9A%84%E6%98%AF%E9%A0%90%E8%A8%AD%E5%91%BD%E4%BB%A4%EF%BC%8C%E6%98%AF%E5%8F%AF%E4%BB%A5%E8%A2%AB%E8%A4%87%E5%AF%AB%E6%8E%89%E7%9A%84">- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">CMD配置的是預設命令，是可以被複寫掉的。而ENTRYPOINT是一定會執行的命令。</span>

</div><div id="bkmrk-%E5%85%A9%E8%80%85%E5%85%B1%E7%94%A8%E7%9A%84%E8%A9%B1%EF%BC%8C%E5%AE%8C%E6%95%B4%E7%9A%84%E5%91%BD%E4%BB%A4%E6%9C%83%E6%98%AFentryp">- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">兩者共用的話，完整的命令會是</span><span class="inline-code author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">ENTRYPOINT + CMD</span>

</div><div id="bkmrk--33"></div><div id="bkmrk-%E5%BB%BA%E7%AB%8Bdocker-image"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">建立docker image</span></div><div id="bkmrk-docker-build-.--t-ap">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker build . -t apache:v1</span>`</div><div id="bkmrk--34"></div><div id="bkmrk-%E8%A4%87%E5%AF%ABcmd"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">複寫CMD</span></div><div id="bkmrk-%23%E7%9B%B4%E6%8E%A5%E8%A4%87%E5%AF%ABcmd%EF%BC%8C%E6%94%B9%E7%82%BA%E9%A1%AF%E7%A4%BA%E7%89%88%E8%99%9F">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#直接複寫CMD，改為顯示版號</span>`</div><div id="bkmrk-docker-run---rm-apac">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker run --rm apache:v1 -v</span>`</div><div id="bkmrk--35"></div><div id="bkmrk-env%E7%92%B0%E5%A2%83%E8%AE%8A%E6%95%B8"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**ENV環境變數**</span></span></div><div id="bkmrk-from-alpine-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>FROM</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> alpine</span>`</div><div id="bkmrk-env-node_env%3D%22develo">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>ENV</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> NODE_ENV="development"</span>`</div><div id="bkmrk--36"></div><div id="bkmrk-arg%E5%82%B3%E5%85%A5%E5%8F%83%E6%95%B8"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**ARG傳入參數**</span></span></div><div id="bkmrk-from-alpine-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>FROM</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> alpine</span>`</div><div id="bkmrk-arg-node_arg">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>ARG </strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">NODE_ARG</span>`</div><div id="bkmrk-env-node_env%3D%22%24%7Bnode">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>ENV</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> NODE_ENV="${NODE_ARG:-development}"</span>`</div><div id="bkmrk-run-echo-%22arg%3D%24%7Bnode">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>RUN</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> echo "ARG=${NODE_ARG}, ENV=${NODE_ENV}"</span>`</div><div id="bkmrk-cmd-echo-%22arg%3D%24%7Bnode">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>CMD</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> echo "ARG=${NODE_ARG}, ENV=${NODE_ENV}"</span>`</div><div id="bkmrk--37"></div><div id="bkmrk-%23-%E5%82%B3%E5%85%A5node_arg">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># 傳入NODE_ARG</span>`</div><div id="bkmrk-docker_buildkit%3D0-do">`<span class="thread-263389414338883775841008 attrcomment attrcommentfirst thread-263389414338883775841008-first author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="comment-extra-inner-span">DOCKER_BUILDKIT=0 </span></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker build --build-arg NODE_ARG=staging .</span>`</div><div id="bkmrk--38"></div><div id="bkmrk-%23-%E5%82%B3%E5%85%A5node_arg-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># 傳入NODE_ARG</span>`</div><div id="bkmrk-docker_buildkit%3D0-do-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">DOCKER_BUILDKIT=0 docker build .</span>`</div><div id="bkmrk--39"></div><div id="bkmrk-copy%E8%88%87add-%E8%A4%87%E8%A3%BD%E6%96%87%E4%BB%B6"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**COPY與ADD 複製文件**</span></span></div><div id="bkmrk-%E5%8A%9F%E8%83%BD%EF%BC%9A%E5%B0%87%E6%AA%94%E6%A1%88%E8%A4%87%E8%A3%BD%E5%88%B0image%E4%B8%AD">- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">功能：將檔案複製到image中</span>

</div><div id="bkmrk-%E5%B7%AE%E7%95%B0%EF%BC%9A">- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">差異：</span>

</div>- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">COPY只能複製本機端的檔案或目錄</span>
- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">ADD可增加url的檔案到docker image</span> <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z h-lparen">(建議改用RUN</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> curl/wget)</span>
- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">ADD可將本機端認可的壓縮格式tar解開後複製進去(要小心)</span>



<div id="bkmrk-%E6%9C%80%E4%BD%B3%E5%AF%A6%E8%B8%90%EF%BC%9A-%E7%9B%A1%E9%87%8F%E4%BD%BF%E7%94%A8copy%E8%80%8C%E4%B8%8D%E6%98%AFadd">- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">最佳實踐： 盡量使用</span><span class="inline-code author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">COPY</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">而不是</span><span class="inline-code author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">ADD</span>

</div><div id="bkmrk--40"></div><div id="bkmrk-%23%E8%A4%87%E8%A3%BDpackage.json%E5%88%B0%2Fapp">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#複製package.json到/app下</span>`</div><div id="bkmrk-copy-package.json-%2Fa">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">COPY package.json /app/</span>`</div><div id="bkmrk--41"></div><div id="bkmrk-%23%E8%A4%87%E8%A3%BD%E6%89%80%E6%9C%89file%E9%96%8B%E9%A0%AD%E7%9A%84%E6%AA%94%E6%A1%88%E5%88%B0%2Fapp%E4%B8%8B">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#複製所有file開頭的檔案到/app下，並改變檔案所屬用戶與群組</span>`</div><div id="bkmrk-copy---chown%3Duser%3Agr">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">COPY --chown=user</span><span class="thread-664457574932181552801007 author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">:group</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> file* /app/</span>`</div><div id="bkmrk--42"></div><div id="bkmrk-%E8%8B%A5%E6%9C%89%E4%BA%9B%E6%AA%94%E6%A1%88%E4%B8%8D%E9%9C%80%E8%A6%81%E5%8A%A0%E5%85%A5%E5%88%B0image%E4%B8%AD%EF%BC%8C-%E5%8F%AF">- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">![light bulb](https://paper.dropboxstatic.com/static/img/ace/emoji/1f4a1.png?version=6.0.0 "light bulb")</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> 若有些檔案不需要加入到image中， 可以建立 </span><span class="inline-code author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">.dockerignore</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> 來忽略喔！</span>

</div><div id="bkmrk--43"></div><div id="bkmrk-workdir%E5%B7%A5%E4%BD%9C%E7%9B%AE%E9%8C%84"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**WORKDIR工作目錄**</span></span></div><div id="bkmrk-%23-hi.txt%E6%9C%83%E5%9C%A8%2F%E7%9B%AE%E9%8C%84%E4%B8%8B">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># hi.txt會在/目錄下</span>`</div><div id="bkmrk-run-echo-%22say-hi%22-%3E-">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN echo "say hi" > hi.txt</span>`</div><div id="bkmrk-workdir-%2Fapp">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">WORKDIR /app</span>`</div><div id="bkmrk-%23-world.txt%E6%9C%83%E5%9C%A8%2Fapp%E7%9A%84%E7%9B%AE%E9%8C%84">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># world.txt會在/app的目錄下</span>`</div><div id="bkmrk-run-echo-%22hello%22-%3E-w">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN echo "hello" > world.txt</span>`</div><div id="bkmrk--44"></div><div id="bkmrk-user%E6%8C%87%E5%AE%9A%E7%95%B6%E5%89%8D%E7%94%A8%E6%88%B6"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**USER指定當前用戶**</span></span></div><div id="bkmrk-%23%E6%96%B0%E5%A2%9Egroup%E8%88%87user">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#新增group與user</span>`</div><div id="bkmrk-run-groupadd--r-redi">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN groupadd -r redis && useradd -r -g redis redis</span>`</div><div id="bkmrk-%23%E6%8C%87%E5%AE%9A%E7%95%B6%E5%89%8D%E7%9A%84user%E7%82%BAredis">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#指定當前的user為redis</span>`</div><div id="bkmrk-user-redis">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">USER redis</span>`</div><div id="bkmrk-%23%E4%BB%A5redis%E7%9A%84%E8%BA%AB%E4%BB%BD%E5%9F%B7%E8%A1%8Credis-se">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#以redis的身份執行redis-server</span>`</div><div id="bkmrk-run-%5B-%22redis-server%22">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN [ "redis-server" ]</span>`</div><div id="bkmrk--45"></div><div id="bkmrk-healthcheck-%E5%81%A5%E5%BA%B7%E6%AA%A2%E6%9F%A5"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**HEALTHCHECK 健康檢查**</span></span></div><div id="bkmrk-from-nginx">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">FROM nginx</span>`</div><div id="bkmrk-run-apt-get-update-%26">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN apt-get update && apt-get install -y curl && </span><span class="thread-590770326760184689811544 attrcomment attrcommentfirst thread-590770326760184689811544-first author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="comment-extra-inner-span">rm -rf /var/lib/apt/lists/*</span></span>`</div><div id="bkmrk-healthcheck---interv">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">HEALTHCHECK --interval=5s --timeout=3s \</span>`</div><div id="bkmrk-%C2%A0-cmd-curl--fs-http%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  CMD curl -fs http://localhost/ || exit 1</span>`</div><div id="bkmrk--46"></div>## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">打造最小的Docker映像檔 </span>

<div id="bkmrk-%E5%A4%9A%E9%9A%8E%E6%AE%B5%E5%BB%BA%E7%BD%AEmulti-stage-bui"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**多階段建置Multi-stage Build**</span></span></div><div id="bkmrk-%23-stage-1-%E5%BB%BA%E7%BD%AE%E7%92%B0%E5%A2%83">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># Stage 1 建置環境</span>`</div><div id="bkmrk-from-alpine-as-build">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">FROM alpine </span><span class="thread-327434999775899771653400 attrcomment attrcommentfirst thread-327434999775899771653400-first author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="comment-extra-inner-span">AS</span></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> build</span>`</div><div id="bkmrk-run-echo-%22hello%22-%3E-m">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN echo "hello" > mytest</span>`</div><div id="bkmrk-%C2%A0-8">` `</div><div id="bkmrk-%23-stage-2-%E5%9F%B7%E8%A1%8C%E7%92%B0%E5%A2%83">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># Stage 2 執行環境</span>`</div><div id="bkmrk-from-alpine-%C2%A0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">FROM alpine  </span>`</div><div id="bkmrk-copy---from%3Dbuild-%2Fm">`<span class="thread-181959338896635756049539 attrcomment attrcommentfirst thread-181959338896635756049539-first author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="comment-extra-inner-span">COPY --from=build</span></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> /mytest .</span>`</div><div id="bkmrk-run-cat-%2Fmytest">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN cat /mytest</span>`</div><div id="bkmrk--47"></div><div id="bkmrk--48"></div><div id="bkmrk-distroless"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**Distroless**</span></span></div><div id="bkmrk-https%3A%2F%2Fgithub.com%2Fg"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://github.com/GoogleContainerTools/distroless","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://github.com/GoogleContainerTools/distroless">[https://github.com/GoogleContainerTools/distroless](https://github.com/GoogleContainerTools/distroless)  
</span></span></div><div id="bkmrk-https%3A%2F%2Fgithub.com%2Fg-0"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://github.com/GoogleContainerTools/distroless/tree/main/examples/go","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://github.com/GoogleContainerTools/distroless/tree/main/examples/go">[https://github.com/GoogleContainerTools/distroless/tree/main/examples/go](https://github.com/GoogleContainerTools/distroless/tree/main/examples/go)  
</span></span></div><div id="bkmrk-main.go"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">main.go</span></div><div id="bkmrk-package-main">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">package main</span>`</div><div id="bkmrk-%C2%A0-9">` `</div><div id="bkmrk-import-%22fmt%22">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">import "fmt"</span>`</div><div id="bkmrk-%C2%A0-10">` `</div><div id="bkmrk-func-main%28%29-%7B">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">func main() {</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-fmt.println%28">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        fmt.Println("Hello, world!")</span>`</div><div id="bkmrk-%7D">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">}</span>`</div><div id="bkmrk--49"></div><div id="bkmrk-dockerfile"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">Dockerfile</span></div><div id="bkmrk-from-golang%3A1.12-as-">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">FROM golang:1.12 as build-env</span>`</div><div id="bkmrk-workdir-%2Fgo%2Fsrc%2Fapp">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">WORKDIR /go/src/app</span>`</div><div id="bkmrk-copy-.-%2Fgo%2Fsrc%2Fapp">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">COPY . /go/src/app</span>`</div><div id="bkmrk-run-go-get--d--v-.%2F.">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN go get -d -v ./...</span>`</div><div id="bkmrk-run-go-build--o-%2Fgo%2F">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN go build -o /go/bin/app</span>`</div><div id="bkmrk-%C2%A0-11">` `</div><div id="bkmrk-from-gcr.io%2Fdistrole">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">FROM gcr.io/distroless/base</span>`</div><div id="bkmrk-copy---from%3Dbuild-en">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">COPY --from=build-env /go/bin/app /</span>`</div><div id="bkmrk-cmd-%5B%22%2Fapp%22%5D">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">CMD ["/app"]</span>`</div><div id="bkmrk--50"></div>## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">建立多重系統架構的映像檔</span>

<div id="bkmrk-%E4%B8%8D%E5%90%8C%E5%9F%B7%E8%A1%8C%E7%92%B0%E5%A2%83%E7%9A%84%E5%95%8F%E9%A1%8C"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**不同執行環境的問題**</span></span></div><div id="bkmrk--51"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"image","url":"https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634268176860_image.png","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"在Apple M1上執行\n","attribs":"*0+c|1+1"},"pool":{"numToAttrib":{"0":["author","d.iX86Va0dCNNJDjPG9iZa3ls0ROswnpuOedberDa3xTLqmSmT1K"]},"attribToNum":{"author,d.iX86Va0dCNNJDjPG9iZa3ls0ROswnpuOedberDa3xTLqmSmT1K":0},"nextNum":1,"dirty":true,"firstDirtyNum":0}}}}]}" data-ace-gallery-urls="https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634268176860_image.png">![](https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634268176860_image.png)</span></span></div><div id="bkmrk-%E4%BD%BF%E7%94%A8buildx%E5%BB%BA%E6%A7%8B%E5%A4%9A%E9%87%8D%E7%B3%BB%E7%B5%B1%E6%9E%B6%E6%A7%8B%E7%9A%84%E6%98%A0%E5%83%8F%E6%AA%94"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**使用Buildx建構多重系統架構的映像檔**</span></span></div><div id="bkmrk-https%3A%2F%2Fdocs.docker.-3"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://docs.docker.com/desktop/multi-arch/","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://docs.docker.com/desktop/multi-arch/">[https://docs.docker.com/desktop/multi-arch/](https://docs.docker.com/desktop/multi-arch/)  
</span></span></div><div id="bkmrk--52"></div><div id="bkmrk-%23%E7%A2%BA%E8%AA%8D%E7%9B%AE%E5%89%8D%E7%9A%84builder%E7%82%BA%E4%BD%95">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#確認目前的builder為何</span>`</div><div id="bkmrk-docker-buildx-ls">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker buildx ls</span>`</div><div id="bkmrk--53"></div><div id="bkmrk-%23%E5%BB%BA%E7%AB%8B%E6%96%B0%E7%9A%84builder%E4%B8%A6%E5%88%87%E6%8F%9B%E9%81%8E%E5%8E%BB">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#建立新的builder並切換過去</span>`</div><div id="bkmrk-docker-buildx-create">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker buildx create --name multiarch --use</span>`</div><div id="bkmrk--54"></div><div id="bkmrk-%E8%8B%A5%E5%9C%A8linux%E7%92%B0%E5%A2%83%E4%B8%AD%E5%BB%BA%E7%AB%8Bbuilder%E7%9A%84"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">![light bulb](https://paper.dropboxstatic.com/static/img/ace/emoji/1f4a1.png?version=6.0.0 "light bulb")</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> 若在linux環境中建立builder的話，需要額外跑qemu-user-static容器，並將driver設為docker-container</span></div><div id="bkmrk-docker-run---rm---pr">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker run --rm --privileged multiarch/qemu-user-static --reset -p yes</span>`</div><div id="bkmrk-docker-buildx-create-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker buildx create --name multiarch --driver docker-container --use</span>`</div><div id="bkmrk--55"></div><div id="bkmrk-%23%E6%AA%A2%E6%9F%A5%E7%9B%AE%E5%89%8D%E7%9A%84builder">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#檢查目前的builder</span>`</div><div id="bkmrk-docker-buildx-inspec">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker buildx inspect --bootstrap</span>`</div><div id="bkmrk-%23%E5%BB%BA%E7%AB%8B%E6%94%AF%E6%8F%B4amd64%E8%88%87arm64%E7%9A%84ima">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#建立支援amd64與arm64的image，並且推送至registry</span>`</div><div id="bkmrk-docker-buildx-build-">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker buildx build . --platform linux/amd64,linux/arm64 --push -t hello:v1</span>`</div># <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">![orangutan](https://paper.dropboxstatic.com/static/img/ace/emoji/1f9a7.png?version=6.0.0 "orangutan")</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> 一次控制多個容器：Docker Compose</span>

## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">Docker Compose 的概念</span>

- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">定義和運行多個容器應用。</span>
- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">使用 YAML 文件來配置應用服務</span>
- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">使用單一命令從配置中創建和啟動所有服務</span>
- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">適用於所有環境：生產、Staging、開發、測試以及 CI 工作流</span>

<div id="bkmrk--56"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"image","url":"https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634196692841_image.png","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"Source: https://share.zabbix.com/virtualization/docker/docker-compose-yml-v2-format-for-zabbix-3-0\n","attribs":"*0+2q|1+1"},"pool":{"numToAttrib":{"0":["author","d.iX86Va0dCNNJDjPG9iZa3ls0ROswnpuOedberDa3xTLqmSmT1K"]},"attribToNum":{"author,d.iX86Va0dCNNJDjPG9iZa3ls0ROswnpuOedberDa3xTLqmSmT1K":0},"nextNum":1,"dirty":true,"firstDirtyNum":0}}}}]}" data-ace-gallery-urls="https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634196692841_image.png">![](https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634196692841_image.png)</span></span></div>## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">Docker Compose 文件格式</span>

<div id="bkmrk-%E5%9F%BA%E6%9C%AC%E6%A0%BC%E5%BC%8F"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**基本格式**</span></span></div><div id="bkmrk-%E9%A0%90%E8%A8%AD%E6%96%87%E4%BB%B6%E5%90%8D%E7%A8%B1%E7%82%BA-docker-compo"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">預設文件名稱為 </span><span class="inline-code author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker-compose.yml</span></div><div id="bkmrk-version%3A-%223%22">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">version: "3"</span>`</div><div id="bkmrk-services%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">services:</span>`</div><div id="bkmrk-%C2%A0-webapp%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  webapp:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%23%E6%8C%87%E5%AE%9Aimage">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    #指定image</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-image%3A-httpd%3Aalp">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    image: httpd:alpine </span>`</div><div id="bkmrk-%C2%A0-%C2%A0-ports%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    ports:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---%2280%3A80%22">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - "80:80"</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-volumes%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    volumes:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---%22%2Fdata%22">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - "/data"</span>`</div><div id="bkmrk--57"></div><div id="bkmrk-build-%E5%BB%BA%E7%AB%8B%E6%98%A0%E5%83%8F%E6%AA%94"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**build 建立映像檔**</span></span></div><div id="bkmrk-version%3A-%273%27">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">version: '3'</span>`</div><div id="bkmrk-services%3A-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">services:</span>`</div><div id="bkmrk-%C2%A0-webapp%3A-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  webapp:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%23%E5%BE%9Edockerfile-alt">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    #從Dockerfile-alternate建立映像檔</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-build%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    build:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-context%3A-.">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      context: .</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-dockerfile%3A-do">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      dockerfile: Dockerfile-alternate</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-args%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      args:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-buildno%3A-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        buildno: 1</span>`</div><div id="bkmrk--58"></div><div id="bkmrk-https%3A%2F%2Fdocs.microso-0"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z url">[https://docs.microsoft.com/zh-tw/visualstudio/docker/tutorials/use-docker-compose](https://docs.microsoft.com/zh-tw/visualstudio/docker/tutorials/use-docker-compose)</span></div><div id="bkmrk--59"></div><div id="bkmrk-https%3A%2F%2Fdocs.docker.-4"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://docs.docker.com/compose/compose-file/compose-file-v3/","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://docs.docker.com/compose/compose-file/compose-file-v3/">[https://docs.docker.com/compose/compose-file/compose-file-v3/](https://docs.docker.com/compose/compose-file/compose-file-v3/)  
</span></span></div>## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">Docker Compose 命令參考</span>

<div id="bkmrk-https%3A%2F%2Fdocs.docker.-5"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://docs.docker.com/compose/reference/","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://docs.docker.com/compose/reference/">[https://docs.docker.com/compose/reference/](https://docs.docker.com/compose/reference/)  
</span></span></div><div id="bkmrk-%E5%9F%B7%E8%A1%8Cdocker-compose"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**執行docker compose**</span></span></div><div id="bkmrk-%23%E5%89%8D%E6%99%AF%E5%9F%B7%E8%A1%8C">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#前景執行</span>`</div><div id="bkmrk-docker-compose-up">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker-compose up</span>`</div><div id="bkmrk-%23%E8%83%8C%E6%99%AF%E5%9F%B7%E8%A1%8C">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#背景執行</span>`</div><div id="bkmrk-docker-compose-up--d">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker-compose up -d</span>`</div><div id="bkmrk--60"></div><div id="bkmrk-%E5%81%9C%E6%AD%A2%2F%E5%95%9F%E5%8B%95docker-compose"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**停止/啟動docker compose**</span></span></div><div id="bkmrk-docker-compose-stop">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker-compose stop</span>`</div><div id="bkmrk--61"></div><div id="bkmrk-%E7%A7%BB%E9%99%A4docker-compose"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**移除docker compose**</span></span></div><div id="bkmrk-docker-compose-down">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker-compose down</span>`</div><div id="bkmrk-%23%E4%B9%9F%E5%90%8C%E6%AD%A5%E7%A7%BB%E9%99%A4volume">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#也同步移除volume</span>`</div><div id="bkmrk-docker-compose-down-">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker-compose down -v</span>`</div><div id="bkmrk--62"></div><div id="bkmrk-%E5%88%97%E5%87%BAcontainers"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**列出containers**</span></span></div><div id="bkmrk-docker-compose-ps">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker-compose ps</span>`</div><div id="bkmrk--63"></div><div id="bkmrk-%E7%9B%A3%E7%9C%8Blogs"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**監看logs**</span></span></div><div id="bkmrk-docker-compose-logs">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker-compose logs</span>`</div><div id="bkmrk-%23follow-logs">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#follow logs</span>`</div><div id="bkmrk-docker-compose-logs-">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker-compose logs -f</span>`</div><div id="bkmrk--64"></div><div id="bkmrk-%E5%9F%B7%E8%A1%8C%E7%A8%8B%E5%BC%8F"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**執行程式**</span></span></div><div id="bkmrk-docker-compose-exec-">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker-compose exec webapp sh</span>`</div><div id="bkmrk--65"></div><div id="bkmrk-%E9%A1%AF%E7%A4%BA%E5%9F%B7%E8%A1%8C%E7%A8%8B%E5%BA%8F"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**顯示執行程序**</span></span></div><div id="bkmrk-docker-compose-top">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker-compose top</span>`</div><div id="bkmrk--66"></div><div id="bkmrk--67"></div>## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">Docker Compose 環境變數</span>

<div id="bkmrk-environment"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**environment**</span></span></div><div id="bkmrk-environment%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">environment:</span>`</div><div id="bkmrk-%C2%A0-rack_env%3A-developm">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  RACK_ENV: development</span>`</div><div id="bkmrk-%C2%A0-show%3A-%27true%27">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  SHOW: 'true'</span>`</div><div id="bkmrk-%C2%A0-session_secret%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  SESSION_SECRET:</span>`</div><div id="bkmrk--68"></div><div id="bkmrk-environment%3A-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">environment:</span>`</div><div id="bkmrk-%C2%A0---rack_env%3Ddevelop">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  - RACK_ENV=development</span>`</div><div id="bkmrk-%C2%A0---show%3Dtrue">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  - SHOW=true</span>`</div><div id="bkmrk-%C2%A0---session_secret">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  - SESSION_SECRET</span>`</div><div id="bkmrk--69"></div><div id="bkmrk-env_file%C2%A0"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**env\_file** </span></span></div><div id="bkmrk-.env"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">.env</span></div><div id="bkmrk-var%3Dhello">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">VAR=hello</span>`</div><div id="bkmrk-myenv%3Ddevelopment">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">MYENV=development</span>`</div><div id="bkmrk--70"></div><div id="bkmrk-env_file%3A-.env">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">env_file: .env</span>`</div><div id="bkmrk--71"></div><div id="bkmrk-env_file%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">env_file:</span>`</div><div id="bkmrk-%C2%A0---.%2Fcommon.env">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  - ./common.env</span>`</div><div id="bkmrk-%C2%A0---.%2Fapps%2Fweb.env">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  - ./apps/web.env</span>`</div><div id="bkmrk-%C2%A0---%2Fopt%2Fruntime_opt">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  - /opt/runtime_opts.env</span>`</div>## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">Docker Compose 健康檢查</span>

<div id="bkmrk-healthcheck%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">healthcheck:</span>`</div><div id="bkmrk-%C2%A0-test%3A-%5B%22cmd%22%2C-%22cur">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  test: ["CMD", "curl", "-f", "http://localhost"]</span>`</div><div id="bkmrk-%C2%A0-interval%3A-1m30s">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  interval: 1m30s</span>`</div><div id="bkmrk-%C2%A0-timeout%3A-10s">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  timeout: 10s</span>`</div><div id="bkmrk-%C2%A0-retries%3A-3">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  retries: 3</span>`</div><div id="bkmrk-%C2%A0-start_period%3A-40s">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  start_period: 40s</span>`</div>## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">Docker Compose 起動順序</span>

<div id="bkmrk-version%3A-%223%22-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">version: "3"</span>`</div><div id="bkmrk-services%3A-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">services:</span>`</div><div id="bkmrk-%C2%A0-web%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  web:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-build%3A-.">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    build: .</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-ports%3A-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    ports:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---%2280%3A8000%22">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - "80:8000"</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-depends_on%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    </span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>depends_on</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---%22db%22">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - "db"</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-command%3A-%5B%22.%2Fwai">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    command: ["./wait-for-it.sh", "db:5432", "--", "python", "app.py"]</span>`</div><div id="bkmrk-%C2%A0-db%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  db:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-image%3A-postgres">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    image: postgres</span>`</div><div id="bkmrk-%E5%B0%8F%E5%B7%A5%E5%85%B7"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**![hammer and pick](https://paper.dropboxstatic.com/static/img/ace/emoji/2692.png?version=6.0.0 "hammer and pick")**</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> **小工具**</span></span></div>- <span class="attrlink url author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">[wait-for-it](https://github.com/vishnubob/wait-for-it)</span>
- <span class="attrlink url author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">[wait-for](https://github.com/Eficode/wait-for)</span>

## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">Docker Compose 磁碟掛載</span>

<div id="bkmrk--72"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"image","url":"https://docs.docker.com/storage/images/types-of-mounts-volume.png","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"volumes on the Docker host\n","attribs":"*0+q|1+1"},"pool":{"numToAttrib":{"0":["author","d.iX86Va0dCNNJDjPG9iZa3ls0ROswnpuOedberDa3xTLqmSmT1K"]},"attribToNum":{"author,d.iX86Va0dCNNJDjPG9iZa3ls0ROswnpuOedberDa3xTLqmSmT1K":0},"nextNum":1,"dirty":true,"firstDirtyNum":0}}}}]}" data-ace-gallery-urls="https://docs.docker.com/storage/images/types-of-mounts-volume.png">![](https://docs.docker.com/storage/images/types-of-mounts-volume.png)</span></span></div><div id="bkmrk--73"></div><div id="bkmrk-long-syntax"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">Long syntax</span></div><div id="bkmrk-version%3A-%223.9%22">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">version: "3.9"</span>`</div><div id="bkmrk-services%3A-2">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">services:</span>`</div><div id="bkmrk-%C2%A0-web%3A-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  web:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-image%3A-nginx%3Aalp">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    image: nginx:alpine</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-volumes%3A-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    </span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>volumes</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---type%3A-volume">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - type: volume</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-source%3A-myda">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        source: mydata</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-target%3A-%2Fdat">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        target: /data</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-volume%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        volume:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-nocopy%3A-tr">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">          </span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>nocopy</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">: true</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---type%3A-bind">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - type: bind</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-source%3A-.%2Fst">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        source: ./static</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-target%3A-%2Fopt">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        target: /opt/app/static</span>`</div><div id="bkmrk-%C2%A0-12">` `</div><div id="bkmrk-%C2%A0-db%3A-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  db:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-image%3A-postgres%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    image: postgres:latest</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-volumes%3A-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    </span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>volumes</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---%22%2Fvar%2Frun%2Fpo">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - "/var/run/postgres/postgres.sock:/var/run/postgres/postgres.sock"</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---%22dbdata%3A%2Fvar">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - "dbdata:/var/lib/postgresql/data"</span>`</div><div id="bkmrk-%C2%A0-13">` `</div><div id="bkmrk-volumes%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>volumes</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">:</span>`</div><div id="bkmrk-%C2%A0-mydata%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  mydata:</span>`</div><div id="bkmrk-%C2%A0-dbdata%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  dbdata:</span>`</div><div id="bkmrk--74"></div><div id="bkmrk-short-syntax"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">Short syntax</span></div><div id="bkmrk-volumes%3A-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">volumes:</span>`</div><div id="bkmrk-%C2%A0-%23-just-specify-a-p">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  # Just specify a path and let the Engine create a volume</span>`</div><div id="bkmrk-%C2%A0---%2Fvar%2Flib%2Fmysql">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  - /var/lib/mysql</span>`</div><div id="bkmrk-%C2%A0-14">` `</div><div id="bkmrk-%C2%A0-%23-specify-an-absol">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  # Specify an absolute path mapping</span>`</div><div id="bkmrk-%C2%A0---%2Fopt%2Fdata%3A%2Fvar%2Fl">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  - /opt/data:/var/lib/mysql</span>`</div><div id="bkmrk-%C2%A0-15">` `</div><div id="bkmrk-%C2%A0-%23-path-on-the-host">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  # Path on the host, relative to the Compose file</span>`</div><div id="bkmrk-%C2%A0---.%2Fcache%3A%2Ftmp%2Fcac">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  - ./cache:/tmp/cache</span>`</div><div id="bkmrk-%C2%A0-16">` `</div><div id="bkmrk-%C2%A0-%23-user-relative-pa">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  # User-relative path</span>`</div><div id="bkmrk-%C2%A0---%7E%2Fconfigs%3A%2Fetc%2Fc">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  - ~/configs:/etc/configs/:ro</span>`</div><div id="bkmrk-%C2%A0-17">` `</div><div id="bkmrk-%C2%A0-%23-named-volume">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  # Named volume</span>`</div><div id="bkmrk-%C2%A0---datavolume%3A%2Fvar%2F">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  - datavolume:/var/lib/mysql</span>`</div># <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">![fireworks](https://paper.dropboxstatic.com/static/img/ace/emoji/1f386.png?version=6.0.0 "fireworks")</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> 如何導入Docker</span>

## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">Docker導入流程</span>

1. <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">確認應用程式使用的程式語言與版本</span>
2. <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">參考相同程式語言的Dockerfile寫法</span>
3. <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">列出應用程式相依的套件(包含系統、語言、字型等等)</span>
4. <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">列出應用程式的設定(環境變數、設定檔)</span>
5. <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">撰寫Dockerfile並測試</span>
6. <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">檢查是否有檔案不該加入映像檔中( </span><span class="inline-code author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">.dockerignore</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">)</span>
7. <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">列出應用程式相依的服務(Database、Redis)</span>
8. <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">確認服務啟動順序並撰寫Docker Compose yaml</span>

## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">實作：Node.js + MongoDB打造API服務</span>

<div id="bkmrk--75"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"image","url":"https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634269999196_image.png","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"source:https://codetree.dev/node-rest-api-tutorial/\n","attribs":"*0+1f|1+1"},"pool":{"numToAttrib":{"0":["author","d.iX86Va0dCNNJDjPG9iZa3ls0ROswnpuOedberDa3xTLqmSmT1K"]},"attribToNum":{"author,d.iX86Va0dCNNJDjPG9iZa3ls0ROswnpuOedberDa3xTLqmSmT1K":0},"nextNum":1,"dirty":true,"firstDirtyNum":0}}}}]}" data-ace-gallery-urls="https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634269999196_image.png">![](https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634269999196_image.png)</span></span></div><div id="bkmrk-https%3A%2F%2Fgithub.com%2Fm"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://github.com/macchiang/node-express-mongo-example","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://github.com/macchiang/node-express-mongo-example">[https://github.com/macchiang/node-express-mongo-example](https://github.com/macchiang/node-express-mongo-example)  
</span></span></div><div id="bkmrk-%E9%80%8F%E9%81%8E-dockerfile-%E9%83%A8%E7%BD%B2-nod"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**透過 Dockerfile 部署 Node.js 環境** </span></span></div><div id="bkmrk-%23-base-image">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># Base image</span>`</div><div id="bkmrk-from-node%3A16-alpine">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">FROM node:16-alpine</span>`</div><div id="bkmrk-%C2%A0-18">` `</div><div id="bkmrk-run-apk-upgrade---no">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN apk upgrade --no-cache</span>`</div><div id="bkmrk-%C2%A0-19">` `</div><div id="bkmrk-%23-create-app-directo">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># Create app directory</span>`</div><div id="bkmrk-run-mkdir--p-%2Fapp">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN mkdir -p /app</span>`</div><div id="bkmrk-workdir-%2Fapp-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">WORKDIR /app</span>`</div><div id="bkmrk-%C2%A0-20">` `</div><div id="bkmrk-%23-install-app-depend">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># Install app dependencies</span>`</div><div id="bkmrk-copy-package.json-pa">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">COPY package.json package-lock.json ./</span>`</div><div id="bkmrk-run-npm-install">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN npm install</span>`</div><div id="bkmrk-%C2%A0-21">` `</div><div id="bkmrk-%23-copy-app-source">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># copy app source</span>`</div><div id="bkmrk-copy-.-%2Fapp">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">COPY . /app</span>`</div><div id="bkmrk-%C2%A0-22">` `</div><div id="bkmrk-%23-service-port">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># service port</span>`</div><div id="bkmrk-expose-3000">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">EXPOSE 3000</span>`</div><div id="bkmrk-%C2%A0-23">` `</div><div id="bkmrk-cmd-%5B-%22node%22%2C-%22index">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">CMD [ "node", "index.js"]</span>`</div><div id="bkmrk--76"></div><div id="bkmrk-docker-compose-yaml"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**Docker Compose YAML**</span></span></div><div id="bkmrk-version%3A-%223.9%22-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">version: "3.9"</span>`</div><div id="bkmrk-services%3A-3">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">services:</span>`</div><div id="bkmrk-%C2%A0-express%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  express:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-build%3A-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    build:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-context%3A-.%2F">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      context: ./</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-dockerfile%3A-do-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      dockerfile: Dockerfile</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-image%3A-express%3Al">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    image: express:latest</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-ports%3A-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    ports:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---3000%3A3000">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - 3000:3000</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-depends_on%3A-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    depends_on:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---mongo">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - mongo</span>`</div><div id="bkmrk-%C2%A0-mongo%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  mongo:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-image%3A-mongo%3A5">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    image: mongo:5</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-ports%3A-2">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    ports:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---27017%3A27017">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - 27017:27017</span>`</div><div id="bkmrk-%C2%A0-24">` `</div><div id="bkmrk-volumes%3A-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">volumes:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-mongo_data%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    mongo_data:</span>`</div><div id="bkmrk--77"></div>## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">![house with garden](https://paper.dropboxstatic.com/static/img/ace/emoji/1f3e1.png?version=6.0.0 "house with garden")</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> 作業-health check的妙用</span>

<div id="bkmrk-%E8%AB%8B%E5%9C%A8node.js-%2B-mongodb%E7%9A%84">- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">請在Node.js + MongoDB的 Docker Compose YAML中加入health check，以確保當MongoDB啟動後，才啟動express</span>

</div># <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">![soccer ball](https://paper.dropboxstatic.com/static/img/ace/emoji/26bd.png?version=6.0.0 "soccer ball")</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> Docker X Python實作：快速打造 LINE Bot</span>

## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">透過 Dockerfile 建立 Python 環境</span>

<div id="bkmrk-%E7%A2%BA%E8%AA%8Dpython%E7%9A%84image%E8%88%87%E7%89%88%E6%9C%AC"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**確認python的image與版本**</span></span></div><div id="bkmrk-https%3A%2F%2Fhub.docker.c"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z url">[https://hub.docker.com/\_/python](https://hub.docker.com/_/python)</span></div><div id="bkmrk--78"></div><div id="bkmrk-%E4%BA%86%E8%A7%A3dockerfile"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**了解Dockerfile**</span></span></div><div id="bkmrk-from-python%3A3-slim-b">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">FROM python:3</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>-</strong></span><span class="attrlink url author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><a class="attrlink" data-target-href="https://hub.docker.com/layers/python/library/python/slim-bullseye/images/sha256-e1b23e3cca282283cfe8c7b14ab184208824337f0c9c64fce284737fa5cb0f4e?context=explore" href="https://hub.docker.com/layers/python/library/python/slim-bullseye/images/sha256-e1b23e3cca282283cfe8c7b14ab184208824337f0c9c64fce284737fa5cb0f4e?context=explore" rel="noreferrer nofollow noopener" target="_blank"><strong>slim-bullseye</strong></a></span>`</div><div id="bkmrk-%C2%A0-25">` `</div><div id="bkmrk-workdir-%2Fusr%2Fsrc%2Fapp">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">WORKDIR /usr/src/app</span>`</div><div id="bkmrk-%C2%A0-26">` `</div><div id="bkmrk-copy-requirements.tx">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">COPY requirements.txt ./</span>`</div><div id="bkmrk-run-pip-install---no">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN pip install --no-cache-dir -r requirements.txt</span>`</div><div id="bkmrk-%C2%A0-27">` `</div><div id="bkmrk-copy-.-.">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">COPY . .</span>`</div><div id="bkmrk-%C2%A0-28">` `</div><div id="bkmrk-cmd-%5B-%22python%22%2C-%22.%2Fy">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">CMD [ "python", "./your-daemon-or-script.py" ]</span>`</div><div id="bkmrk-%C2%A0-29">` `</div>## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">Flask + Redis 打造計數器</span>

<div id="bkmrk-https%3A%2F%2Fgithub.com%2Fm-0"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://github.com/macchiang/hit-counter","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://github.com/macchiang/hit-counter">[https://github.com/macchiang/hit-counter](https://github.com/macchiang/hit-counter)  
</span></span></div><div id="bkmrk-docker-compose.yml"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker-compose.yml</span></div><div id="bkmrk-version%3A-%272%27">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">version: '2'</span>`</div><div id="bkmrk-services%3A-4">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">services:</span>`</div><div id="bkmrk-%C2%A0-myapp%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  myapp:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-build%3A-.-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    build: .</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-ports%3A-3">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    ports:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0--%2280%3A5000%22">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">     - "80:5000"</span>`</div><div id="bkmrk-%C2%A0-redis-lb%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  redis-lb:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-image%3A-%22redis%3Aal">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    image: "redis:alpine"</span>`</div>## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">構建 Django Web server</span>

<div id="bkmrk-https%3A%2F%2Fgithub.com%2Fm-1"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://github.com/macchiang/django-todolist","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://github.com/macchiang/django-todolist">[https://github.com/macchiang/django-todolist](https://github.com/macchiang/django-todolist)  
</span></span></div><div id="bkmrk-https%3A%2F%2Fdeveloper.mo"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Django/Introduction","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Django/Introduction">[https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Django/Introduction](https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Django/Introduction)  
</span></span></div><div id="bkmrk-%E5%85%88%E4%BA%86%E8%A7%A3%E5%B0%88%E6%A1%88%E7%9A%84%E5%AE%89%E8%A3%9D%E6%96%B9%E5%BC%8F"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**先了解專案的安裝方式**</span></span></div><div id="bkmrk-dockerfile-0"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**Dockerfile**</span></span></div><div id="bkmrk-from-python%3A3-slim-b-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">FROM python:3</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>-</strong></span><span class="attrlink url author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><a class="attrlink" data-target-href="https://hub.docker.com/layers/python/library/python/slim-bullseye/images/sha256-e1b23e3cca282283cfe8c7b14ab184208824337f0c9c64fce284737fa5cb0f4e?context=explore" href="https://hub.docker.com/layers/python/library/python/slim-bullseye/images/sha256-e1b23e3cca282283cfe8c7b14ab184208824337f0c9c64fce284737fa5cb0f4e?context=explore" rel="noreferrer nofollow noopener" target="_blank"><strong>slim-bullseye</strong></a></span>`</div><div id="bkmrk-%C2%A0-30">` `</div><div id="bkmrk-workdir-%2Fapp-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">WORKDIR /app</span>`</div><div id="bkmrk-%C2%A0-31">` `</div><div id="bkmrk-copy-requirements.tx-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">COPY requirements.txt ./</span>`</div><div id="bkmrk-run-pip-install---no-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN pip install --no-cache-dir -r requirements.txt</span>`</div><div id="bkmrk-%C2%A0-32">` `</div><div id="bkmrk-copy-todo%2F%2A-.">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">COPY todo/* .</span>`</div><div id="bkmrk-run-python-manage.py">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN python manage.py migrate</span>`</div><div id="bkmrk-cmd-%5B-%22python%22%2C-%22man">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">CMD [ "python", "manage.py", "runserver" ]</span>`</div><div id="bkmrk--79"></div><div id="bkmrk-docker-compose.yaml"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**docker-compose.yaml**</span></span></div><div id="bkmrk-version%3A-%273.9%27">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">version: '3.9'</span>`</div><div id="bkmrk-services%3A-5">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">services:</span>`</div><div id="bkmrk-%C2%A0-myweb%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  myweb:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-build%3A-.-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    build: .</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-ports%3A-4">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    ports:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0--%228000%3A8000%22">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">     - "8000:8000"</span>`</div>## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">部署 LINE Bot 應用</span>

<div id="bkmrk-https%3A%2F%2Fgithub.com%2Fm-2"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://github.com/macchiang/line-bot-tutorial","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://github.com/macchiang/line-bot-tutorial">[https://github.com/macchiang/line-bot-tutorial](https://github.com/macchiang/line-bot-tutorial)  
</span></span></div><div id="bkmrk-https%3A%2F%2Fgithub.com%2Fl"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://github.com/line/line-bot-sdk-python/tree/master/examples/simple-server-echo","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://github.com/line/line-bot-sdk-python/tree/master/examples/simple-server-echo">[https://github.com/line/line-bot-sdk-python/tree/master/examples/simple-server-echo](https://github.com/line/line-bot-sdk-python/tree/master/examples/simple-server-echo)  
</span></span></div><div id="bkmrk--80"></div><div id="bkmrk-%E5%9C%A8%E4%BD%A0%E9%96%8B%E5%A7%8B%E4%B9%8B%E5%89%8D"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**在你開始之前**</span></span></div><div id="bkmrk-%E7%A2%BA%E4%BF%9D%E6%82%A8%E5%85%B7%E6%9C%89%E4%BB%A5%E4%B8%8B%E5%85%A7%E5%AE%B9%EF%BC%9A"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">確保您具有以下內容：</span></div>- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">擁有 Line 帳號</span>
- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">擁有 </span><span class="attrlink url author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">[Heroku](https://www.heroku.com/)</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> 帳戶（您可以免費創建一個）</span>

<div id="bkmrk--81"></div><div id="bkmrk-%E5%BB%BA%E7%AB%8Bline-bot-%E9%A0%BB%E9%81%93"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**建立LINE Bot 頻道**</span></span></div>- <span class="attrlink url author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">[Messaging API 介紹](https://developers.line.biz/zh-hant/docs/messaging-api/overview/)</span>
- <span class="attrlink url author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">[開始使用 Messaging API](https://developers.line.biz/zh-hant/docs/messaging-api/getting-started/)</span>
- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">取得 </span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**Channel access token**</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> 與 </span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**Channel secret**</span>

<div id="bkmrk--82"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"image","url":"https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636469446904_image.png","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"Channel secret\n","attribs":"*0+e|1+1"},"pool":{"numToAttrib":{"0":["author","d.iX86Va0dCNNJDjPG9iZa3ls0ROswnpuOedberDa3xTLqmSmT1K"]},"attribToNum":{"author,d.iX86Va0dCNNJDjPG9iZa3ls0ROswnpuOedberDa3xTLqmSmT1K":0},"nextNum":1,"dirty":true,"firstDirtyNum":0}}}}]}" data-ace-gallery-urls="https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636469446904_image.png">![](https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636469446904_image.png)</span></span></div><div id="bkmrk--83"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"image","url":"https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636469390721_image.png","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"Channel access token\n","attribs":"*0+k|1+1"},"pool":{"numToAttrib":{"0":["author","d.iX86Va0dCNNJDjPG9iZa3ls0ROswnpuOedberDa3xTLqmSmT1K"]},"attribToNum":{"author,d.iX86Va0dCNNJDjPG9iZa3ls0ROswnpuOedberDa3xTLqmSmT1K":0},"nextNum":1,"dirty":true,"firstDirtyNum":0}}}}]}" data-ace-gallery-urls="https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636469390721_image.png">![](https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636469390721_image.png)</span></span></div>- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">關閉預設罐頭回覆訊息</span>

<div id="bkmrk--84"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"image","url":"https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636504016837_image.png","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636504016837_image.png">![](https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636504016837_image.png)</span></span></div><div id="bkmrk-%E5%BB%BA%E7%AB%8Bdocker-image-0"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**建立Docker Image**</span></span></div><div id="bkmrk-from-python%3A3-slim-b-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">FROM python:3-slim-bullseye</span>`</div><div id="bkmrk-workdir-%2Fapp-2">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">WORKDIR /app</span>`</div><div id="bkmrk-copy-requirements.tx-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">COPY requirements.txt ./</span>`</div><div id="bkmrk-run-pip-install---no-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN pip install --no-cache-dir -r requirements.txt</span>`</div><div id="bkmrk-copy-app.py-.">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">COPY app.py .</span>`</div><div id="bkmrk-cmd-%5B-%22python%22%2C-%22app">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">CMD [ "python", "app.py"]</span>`</div><div id="bkmrk--85"></div><div id="bkmrk-docker-build-.--t-li">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker build . -t line-bot:latest</span>`</div><div id="bkmrk--86"></div><div id="bkmrk-%E5%AE%89%E8%A3%9Dheroku-cli"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**安裝Heroku CLI**</span></span></div><div id="bkmrk-https%3A%2F%2Fdevcenter.he"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://devcenter.heroku.com/articles/heroku-cli#download-and-install","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://devcenter.heroku.com/articles/heroku-cli#download-and-install">[https://devcenter.heroku.com/articles/heroku-cli#download-and-install](https://devcenter.heroku.com/articles/heroku-cli#download-and-install)  
</span></span></div><div id="bkmrk-%E5%BB%BA%E7%AB%8Bheroku-app"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**建立Heroku App**</span></span></div><div id="bkmrk-%23%E7%99%BB%E5%85%A5heroku">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#登入heroku</span>`</div><div id="bkmrk-heroku-login">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">heroku login</span>`</div><div id="bkmrk-%23%E7%99%BB%E5%85%A5heroku-registry">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#登入heroku registry</span>`</div><div id="bkmrk-heroku-container%3Alog">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">heroku container:login</span>`</div><div id="bkmrk-%23%E5%BB%BA%E7%AB%8Bapp">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#建立app</span>`</div><div id="bkmrk-heroku-create">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">heroku create</span>`</div><div id="bkmrk-%C2%A0-33">` `</div><div id="bkmrk-%23creating-app...-don">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#Creating app... done, ⬢ morning-journey-43865</span>`</div><div id="bkmrk-%23https%3A%2F%2Fmorning-jou">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#</span><span class="highlight author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z" data-highlight-color="#FFBFB5"><strong>https://morning-journey-43865.herokuapp.com/</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> | https://git.heroku.com/morning-journey-43865.git</span>`</div><div id="bkmrk-%C2%A0-34">` `</div><div id="bkmrk-%23%E6%8E%A8%E9%80%81docker-image%E8%87%B3hero">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#推送docker image至heroku registry</span>`</div><div id="bkmrk-docker-tag-line-bot%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker tag line-bot:latest registry.heroku.com/</span><span class="highlight author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z" data-highlight-color="#F7CC62"><strong>morning-journey-43865</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">/web</span>`</div><div id="bkmrk-docker-push-registry">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker push registry.heroku.com/</span><span class="highlight author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z" data-highlight-color="#F7CC62"><strong>morning-journey-43865</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">/web</span>`</div><div id="bkmrk-%C2%A0-35">` `</div><div id="bkmrk-%C2%A0-36">` `</div><div id="bkmrk-%23%E8%A8%AD%E5%AE%9A%E7%92%B0%E5%A2%83%E8%AE%8A%E6%95%B8">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#設定環境變數</span>`</div><div id="bkmrk-heroku-config%3Aset-ch">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">heroku config:set CHANNEL_ACCESS_TOKEN=xxxxxxxxx</span>`</div><div id="bkmrk-heroku-config%3Aset-ch-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">heroku config:set CHANNEL_SECRET=xxxxxxxxx</span>`</div><div id="bkmrk-%C2%A0-37">` `</div><div id="bkmrk-%23deploy-docker-image">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#deploy docker image</span>`</div><div id="bkmrk-heroku-container%3Arel">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">heroku container:release web</span>`</div><div id="bkmrk--87"></div><div id="bkmrk-%E8%A8%AD%E5%AE%9Awebhook"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**設定Webhook**</span></span></div><div id="bkmrk--88"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"image","url":"https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636471948301_image.png","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636471948301_image.png">![](https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636471948301_image.png)</span></span></div><div id="bkmrk--89"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"image","url":"https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636471973201_image.png","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636471973201_image.png">![](https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636471973201_image.png)</span></span></div><div id="bkmrk--90"></div><div id="bkmrk--91"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"image","url":"https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636504063885_image.png","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636504063885_image.png">![](https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636504063885_image.png)</span></span></div><div id="bkmrk-https%3A%2F%2Fdevcenter.he-0"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://devcenter.heroku.com/articles/container-registry-and-runtime","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://devcenter.heroku.com/articles/container-registry-and-runtime">[https://devcenter.heroku.com/articles/container-registry-and-runtime](https://devcenter.heroku.com/articles/container-registry-and-runtime)  
</span></span></div><div id="bkmrk-%E5%8A%A0%E5%85%A5%E5%A5%BD%E5%8F%8B%E8%88%87%E6%B8%AC%E8%A9%A6"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**加入好友與測試**</span></span></div><div id="bkmrk--92"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"image","url":"https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636504317204_image.png","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636504317204_image.png">![](https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636504317204_image.png)</span></span></div><div id="bkmrk--93"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"image","url":"https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636504378568_image.png","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636504378568_image.png">![](https://paper-attachments.dropbox.com/s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636504378568_image.png)</span></span></div><div id="bkmrk-debug"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**Debug**</span></span></div><div id="bkmrk-%E2%9D%AF-heroku-logs---tail">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">❯ </span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>heroku logs --tail</strong></span>`</div><div id="bkmrk-2021-11-09t15%3A17%3A06.">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:17:06.932541+00:00 app[api]: Release v1 created by user macchiang@gmail.com</span>`</div><div id="bkmrk-2021-11-09t15%3A17%3A06.-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:17:06.932541+00:00 app[api]: Initial release by user macchiang@gmail.com</span>`</div><div id="bkmrk-2021-11-09t15%3A17%3A07.">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:17:07.532399+00:00 app[api]: Release v2 created by user macchiang@gmail.com</span>`</div><div id="bkmrk-2021-11-09t15%3A17%3A07.-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:17:07.532399+00:00 app[api]: Enable Logplex by user macchiang@gmail.com</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A12.">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:12.485507+00:00 app[api]: Deployed web (1be43e5135d6) by user macchiang@gmail.com</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A12.-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:12.485507+00:00 app[api]: Release v3 created by user macchiang@gmail.com</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A12.-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:12.503987+00:00 app[api]: Scaled to web@1:Free by user macchiang@gmail.com</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A15.">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:15.916499+00:00 heroku[web.1]: Starting process with command `python app.py`</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A18.">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:18.365293+00:00 app[web.1]: Traceback (most recent call last):</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A18.-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:18.365308+00:00 app[web.1]: File "/app/app.py", line 15, in <module></span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A18.-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:18.365410+00:00 app[web.1]: line_bot_api = LineBotApi(os.getenv('CHANNEL_ACCESS_TOKEN'))</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A18.-2">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:18.365411+00:00 app[web.1]: File "/usr/local/lib/python3.10/site-packages/linebot/api.py", line 63, in __init__</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A18.-3">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:18.365516+00:00 app[web.1]: 'Authorization': 'Bearer ' + channel_access_token,</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A18.-4">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:18.374292+00:00 app[web.1]: TypeError: can only concatenate str (not "NoneType") to str</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A18.-5">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:18.660220+00:00 heroku[web.1]: Process exited with status 1</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A19.">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:19.074969+00:00 heroku[web.1]: State changed from starting to crashed</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A19.-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:19.218365+00:00 heroku[web.1]: State changed from crashed to starting</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A22.">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:22.107500+00:00 heroku[web.1]: Starting process with command `python app.py`</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A23.">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:23.219784+00:00 app[web.1]: Traceback (most recent call last):</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A23.-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:23.219806+00:00 app[web.1]: File "/app/app.py", line 15, in <module></span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A23.-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:23.219930+00:00 app[web.1]: line_bot_api = LineBotApi(os.getenv('CHANNEL_ACCESS_TOKEN'))</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A23.-2">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:23.219934+00:00 app[web.1]: File "/usr/local/lib/python3.10/site-packages/linebot/api.py", line 63, in __init__</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A23.-3">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:23.219959+00:00 app[web.1]: 'Authorization': 'Bearer ' + channel_access_token,</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A23.-4">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:23.219961+00:00 app[web.1]: TypeError: can only concatenate str (not "NoneType") to str</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A23.-5">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:23.417290+00:00 heroku[web.1]: Process exited with status 1</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A23.-6">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:23.492672+00:00 heroku[web.1]: State changed from starting to crashed</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A38.">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:38.443725+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/" host=morning-journey-43865.herokuapp.com request_id=b91dba06-950e-4887-840e-60fc5986866f fwd="36.227.157.168" dyno= connect= service= status=503 bytes= protocol=https</span>`</div><div id="bkmrk-2021-11-09t15%3A22%3A39.">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">2021-11-09T15:22:39.424038+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/favicon.ico" host=morning-journey-43865.herokuapp.com request_id=becc5a52-35a7-4557-b751-02d382a2b67e fwd="36.227.157.168" dyno= connect= service= status=503 bytes= protocol=https</span>`</div># <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">![american football](https://paper.dropboxstatic.com/static/img/ace/emoji/1f3c8.png?version=6.0.0 "american football")</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> Docker X PHP實作：架設一個 WordPress 網站</span>

## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">透過 Dockerfile 部署 PHP 環境</span>

<div id="bkmrk-%E5%85%88%E5%BE%9Ephp%E5%AE%98%E6%96%B9dockerfile%E5%AD%B8%E8%B5%B7"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**先從**</span><span class="attrlink url author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">[**Php官方**](https://hub.docker.com/_/php)</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**Dockerfile學起**</span></span></div><div id="bkmrk-https%3A%2F%2Fgithub.com%2Fm-3"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://github.com/macchiang/php-hello-world","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://github.com/macchiang/php-hello-world">[https://github.com/macchiang/php-hello-world](https://github.com/macchiang/php-hello-world)  
</span></span></div><div id="bkmrk-dockerfile-1"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">Dockerfile</span></div><div id="bkmrk-from-php%3A7.3-apache">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">FROM php:7.3-apache</span>`</div><div id="bkmrk-copy-index.php-%2Fvar%2F">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">COPY index.php /var/www/html/</span>`</div><div id="bkmrk--94"></div><div id="bkmrk-%E5%BB%BA%E7%AB%8Bdocker-image%E8%88%87conta"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">建立Docker image與Container</span></div><div id="bkmrk-docker-build-.--t-he">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker build . -t hello:latest</span>`</div><div id="bkmrk-docker-run--it---rm--0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker run -it --rm -p 8080:80 hello:latest</span>`</div><div id="bkmrk-https%3A%2F%2Fwww.astralwe"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://www.astralweb.com.tw/what-is-differences-between-fastcgi-php-fpm/","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://www.astralweb.com.tw/what-is-differences-between-fastcgi-php-fpm/">[https://www.astralweb.com.tw/what-is-differences-between-fastcgi-php-fpm/](https://www.astralweb.com.tw/what-is-differences-between-fastcgi-php-fpm/)  
</span></span></div>## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">將 MySQL 加入部署行列</span>

<div id="bkmrk-https%3A%2F%2Fgithub.com%2Fm-4"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://github.com/macchiang/php-docker-simple","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://github.com/macchiang/php-docker-simple">[https://github.com/macchiang/php-docker-simple](https://github.com/macchiang/php-docker-simple)  
</span></span></div><div id="bkmrk-docker-compose.yml-0"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker-compose.yml</span></div><div id="bkmrk-%23-use-root%2Fexample-u">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># Use root/example user/password credentials</span>`</div><div id="bkmrk-version%3A-%273.1%27">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">version: '3.1'</span>`</div><div id="bkmrk-%C2%A0-38">` `</div><div id="bkmrk-services%3A-6">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">services:</span>`</div><div id="bkmrk-%C2%A0-php%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  php:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-build%3A-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    build:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-context%3A-.-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      context: .</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-dockerfile%3A-do-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      dockerfile: Dockerfile</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-ports%3A-5">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    ports:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---80%3A80">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - 80:80</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-volumes%3A-2">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    volumes:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---.%2Fsrc%3A%2Fvar%2Fw">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - ./src:/var/www/html/</span>`</div><div id="bkmrk-%C2%A0-39">` `</div><div id="bkmrk-%C2%A0-db%3A-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  db:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-image%3A-mysql">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    image: mysql</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-command%3A---defau">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    command: --default-authentication-plugin=mysql_native_password</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-restart%3A-always">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    restart: always</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-environment%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    environment:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-mysql_root_pas">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      MYSQL_ROOT_PASSWORD: example</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-volumes%3A-3">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    volumes:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---mysql-data%3A%2F">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - mysql-data:/var/lib/mysql</span>`</div><div id="bkmrk-%C2%A0-40">` `</div><div id="bkmrk-%C2%A0-adminer%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  adminer:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-image%3A-adminer">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    image: adminer</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-restart%3A-always-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    restart: always</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-ports%3A-6">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    ports:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---8080%3A8080">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - 8080:8080</span>`</div><div id="bkmrk-%C2%A0-41">` `</div><div id="bkmrk-volumes%3A-2">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">volumes:</span>`</div><div id="bkmrk-%C2%A0-mysql-data%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  mysql-data:</span>`</div>## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">建立WordPress 網站</span>

<div id="bkmrk-%E5%86%8D%E7%94%B1wordpresss%E5%AE%98%E6%96%B9docker"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**再由**</span><span class="attrlink url author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">[**WordPresss官方Dockerfile學起**](https://github.com/docker-library/wordpress/blob/master/latest/php7.3/apache/Dockerfile)</span></span></div><div id="bkmrk-%C2%A0%23">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> #</span>`</div><div id="bkmrk-%23-note%3A-this-dockerf">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># NOTE: THIS DOCKERFILE IS GENERATED VIA "apply-templates.sh"</span>`</div><div id="bkmrk-%23">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#</span>`</div><div id="bkmrk-%23-please-do-not-edit">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># PLEASE DO NOT EDIT IT DIRECTLY.</span>`</div><div id="bkmrk-%23-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">#</span>`</div><div id="bkmrk-%C2%A0-42">` `</div><div id="bkmrk-from-php%3A7.3-apache-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">FROM php:7.3-apache</span>`</div><div id="bkmrk-%C2%A0-43">` `</div><div id="bkmrk-%23-persistent-depende">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># persistent dependencies</span>`</div><div id="bkmrk-run-set--eux%3B-%5C">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN set -eux; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-apt-get-upda">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        apt-get update; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-apt-get-inst">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        apt-get install -y --no-install-recommends \</span>`</div><div id="bkmrk-%23-ghostscript-is-req">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># Ghostscript is required for rendering PDF previews</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-ghos">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                ghostscript \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3B-%5C">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        ; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-rm--rf-%2Fvar%2F">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        rm -rf /var/lib/apt/lists/*</span>`</div><div id="bkmrk-%C2%A0-44">` `</div><div id="bkmrk-%23-install-the-php-ex">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># install the PHP extensions we need (https://make.wordpress.org/hosting/handbook/handbook/server-environment/#php-extensions)</span>`</div><div id="bkmrk-run-set--ex%3B-%5C">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN set -ex; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%5C">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-savedaptmark">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        savedAptMark="$(apt-mark showmanual)"; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%5C-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-apt-get-upda-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        apt-get update; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-apt-get-inst-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        apt-get install -y --no-install-recommends \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-libf">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                libfreetype6-dev \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-libj">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                libjpeg-dev \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-libm">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                libmagickwand-dev \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-libp">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                libpng-dev \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-libw">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                libwebp-dev \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-libz">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                libzip-dev \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3B-%5C-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        ; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%5C-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-docker-php-e">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        </span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>docker-php-ext-configure</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> gd \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0---wi">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                --with-freetype-dir=/usr \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0---wi-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                --with-jpeg-dir=/usr \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0---wi-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                --with-png-dir=/usr \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0---wi-2">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                --with-webp-dir=/usr \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3B-%5C-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        ; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-docker-php-e-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        </span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>docker-php-ext-install</strong></span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> -j "$(nproc)" \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-bcma">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                bcmath \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-exif">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                exif \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-gd-%5C">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                gd \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-mysq">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                mysqli \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-zip-">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                zip \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%3B-%5C-2">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        ; \</span>`</div><div id="bkmrk-%23-https%3A%2F%2Fpecl.php.n">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># https://pecl.php.net/package/imagick</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-pecl-install">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        pecl install imagick-3.5.0; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-docker-php-e-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        docker-php-ext-enable imagick; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-rm--r-%2Ftmp%2Fp">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        rm -r /tmp/pear; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%5C-2">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        \</span>`</div><div id="bkmrk-%23-reset-apt-mark%27s-%22">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-apt-mark-aut">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        apt-mark auto '.*' > /dev/null; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-apt-mark-man">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        apt-mark manual $savedAptMark; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-ldd-%22%24%28php--">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        ldd "$(php -r 'echo ini_get("extension_dir");')"/*.so \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7C-aw">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                | awk '/=>/ { print $3 }' \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7C-so">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                | sort -u \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7C-xa">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                | xargs -r dpkg-query -S \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7C-cu">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                | cut -d: -f1 \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7C-so-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                | sort -u \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7C-xa-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                | xargs -rt apt-mark manual; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%5C-3">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-apt-get-purg">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-rm--rf-%2Fvar%2F-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        rm -rf /var/lib/apt/lists/*</span>`</div><div id="bkmrk-%C2%A0-45">` `</div><div id="bkmrk-%23-set-recommended-ph">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># set recommended PHP.ini settings</span>`</div><div id="bkmrk-%23-see-https%3A%2F%2Fsecure">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># see https://secure.php.net/manual/en/opcache.installation.php</span>`</div><div id="bkmrk-run-set--eux%3B-%5C-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN set -eux; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-docker-php-e-2">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        docker-php-ext-enable opcache; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7B-%5C">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        { \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'opcache.memory_consumption=128'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'opcache.interned_strings_buffer=8'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'opcache.max_accelerated_files=4000'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-2">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'opcache.revalidate_freq=2'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-3">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'opcache.fast_shutdown=1'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7D-%3E-%2Fusr%2Floc">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        } > /usr/local/etc/php/conf.d/opcache-recommended.ini</span>`</div><div id="bkmrk-%23-https%3A%2F%2Fwordpress.">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># https://wordpress.org/support/article/editing-wp-config-php/#configure-error-logging</span>`</div><div id="bkmrk-run-%7B-%5C">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN { \</span>`</div><div id="bkmrk-%23-https%3A%2F%2Fwww.php.ne">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># https://www.php.net/manual/en/errorfunc.constants.php</span>`</div><div id="bkmrk-%23-https%3A%2F%2Fgithub.com">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># https://github.com/docker-library/wordpress/issues/420#issuecomment-517839670</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-4">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'error_reporting = E_ERROR | E_WARNING | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING | E_RECOVERABLE_ERROR'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-5">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'display_errors = Off'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-6">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'display_startup_errors = Off'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-7">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'log_errors = On'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-8">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'error_log = /dev/stderr'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-9">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'log_errors_max_len = 1024'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-10">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'ignore_repeated_errors = On'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-11">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'ignore_repeated_source = Off'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-12">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'html_errors = Off'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7D-%3E-%2Fusr%2Floc-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        } > /usr/local/etc/php/conf.d/error-logging.ini</span>`</div><div id="bkmrk-%C2%A0-46">` `</div><div id="bkmrk-run-set--eux%3B-%5C-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN set -eux; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-a2enmod-rewr">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        a2enmod rewrite expires; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%5C-4">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        \</span>`</div><div id="bkmrk-%23-https%3A%2F%2Fhttpd.apac">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># https://httpd.apache.org/docs/2.4/mod/mod_remoteip.html</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-a2enmod-remo">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        a2enmod remoteip; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7B-%5C-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        { \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-13">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'RemoteIPHeader X-Forwarded-For'; \</span>`</div><div id="bkmrk-%23-these-ip-ranges-ar">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># these IP ranges are reserved for "private" use and should thus *usually* be safe inside Docker</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-14">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'RemoteIPTrustedProxy 10.0.0.0/8'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-15">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'RemoteIPTrustedProxy 172.16.0.0/12'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-16">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'RemoteIPTrustedProxy 192.168.0.0/16'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-17">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'RemoteIPTrustedProxy 169.254.0.0/16'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-18">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'RemoteIPTrustedProxy 127.0.0.0/8'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7D-%3E-%2Fetc%2Fapa">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        } > /etc/apache2/conf-available/remoteip.conf; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-a2enconf-rem">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        a2enconf remoteip; \</span>`</div><div id="bkmrk-%23-https%3A%2F%2Fgithub.com-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># https://github.com/docker-library/wordpress/issues/383#issuecomment-507886512</span>`</div><div id="bkmrk-%23-%28replace-all-insta">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># (replace all instances of "%h" with "%a" in LogFormat)</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-find-%2Fetc%2Fap">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        find /etc/apache2 -type f -name '*.conf' -exec sed -ri 's/([[:space:]]*LogFormat[[:space:]]+"[^"]*)%h([^"]*")/\1%a\2/g' '{}' +</span>`</div><div id="bkmrk-%C2%A0-47">` `</div><div id="bkmrk-run-set--eux%3B-%5C-2">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN set -eux; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-version%3D%275.8">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        version='5.8.2'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-sha1%3D%27c3b1b5">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        sha1='c3b1b59553eafbf301c83b14c5eeae4cf1c86044'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%5C-5">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-curl--o-word">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        curl -o wordpress.tar.gz -fL "https://wordpress.org/wordpress-$version.tar.gz"; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-%22%24sha1-">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        echo "$sha1 *wordpress.tar.gz" | sha1sum -c -; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%5C-6">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        \</span>`</div><div id="bkmrk-%23-upstream-tarballs-">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># upstream tarballs include ./wordpress/ so this gives us /usr/src/wordpress</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-tar--xzf-wor">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        tar -xzf wordpress.tar.gz -C /usr/src/; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-rm-wordpress">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        rm wordpress.tar.gz; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%5C-7">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        \</span>`</div><div id="bkmrk-%23-https%3A%2F%2Fwordpress.-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># https://wordpress.org/support/article/htaccess/</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%5B-%21--e-%2Fusr%2F">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        [ ! -e /usr/src/wordpress/.htaccess ]; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7B-%5C-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        { \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-19">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo '# BEGIN WordPress'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-20">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo ''; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-21">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'RewriteEngine On'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-22">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-23">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'RewriteBase /'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-24">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'RewriteRule ^index\.php$ - [L]'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-25">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'RewriteCond %{REQUEST_FILENAME} !-f'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-26">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'RewriteCond %{REQUEST_FILENAME} !-d'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-27">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo 'RewriteRule . /index.php [L]'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-28">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo ''; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-echo-29">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                echo '# END WordPress'; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%7D-%3E-%2Fusr%2Fsrc">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        } > /usr/src/wordpress/.htaccess; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%5C-8">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-chown--r-www">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        chown -R www-data:www-data /usr/src/wordpress; \</span>`</div><div id="bkmrk-%23-pre-create-wp-cont">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># pre-create wp-content (and single-level children) for folks who want to bind-mount themes, etc so permissions are pre-created properly instead of root:root</span>`</div><div id="bkmrk-%23-wp-content%2Fcache%3A-">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># wp-content/cache: https://github.com/docker-library/wordpress/issues/534#issuecomment-705733507</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-mkdir-wp-con">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        mkdir wp-content; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-for-dir-in-%2F">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        for dir in /usr/src/wordpress/wp-content/*/ cache; do \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-dir%3D">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                dir="$(basename "${dir%/}")"; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-%C2%A0-mkdi">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">                mkdir "wp-content/$dir"; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-done%3B-%5C">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        done; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-chown--r-www-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        chown -R www-data:www-data wp-content; \</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-chmod--r-777">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        chmod -R 777 wp-content</span>`</div><div id="bkmrk-%C2%A0-48">` `</div><div id="bkmrk-volume-%2Fvar%2Fwww%2Fhtml">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">VOLUME /var/www/html</span>`</div><div id="bkmrk-%C2%A0-49">` `</div><div id="bkmrk-copy---chown%3Dwww-dat">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">COPY --chown=www-data:www-data wp-config-docker.php /usr/src/wordpress/</span>`</div><div id="bkmrk-copy-docker-entrypoi">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">COPY docker-entrypoint.sh /usr/local/bin/</span>`</div><div id="bkmrk-%C2%A0-50">` `</div><div id="bkmrk-entrypoint-%5B%22docker-">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">ENTRYPOINT ["docker-entrypoint.sh"]</span>`</div><div id="bkmrk-cmd-%5B%22apache2-foregr">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">CMD ["apache2-foreground"]</span>`</div><div id="bkmrk--95"></div><div id="bkmrk-https%3A%2F%2Fgithub.com%2Fm-5"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://github.com/macchiang/wordpress-demo","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://github.com/macchiang/wordpress-demo">[https://github.com/macchiang/wordpress-demo](https://github.com/macchiang/wordpress-demo)  
</span></span></div><div id="bkmrk-docker-compose.yaml-0"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker-compose.yaml</span></div><div id="bkmrk-version%3A-%223.3%22">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">version: "3.3"</span>`</div><div id="bkmrk-services%3A-7">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">services:</span>`</div><div id="bkmrk-%C2%A0-db%3A-2">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  db:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-image%3A-mysql-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    image: mysql</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-volumes%3A-4">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    volumes:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---db_data%3A%2Fvar">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - db_data:/var/lib/mysql</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-restart%3A-always-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    restart: always</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-environment%3A-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    environment:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-mysql_root_pas-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      MYSQL_ROOT_PASSWORD: somewordpress</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-mysql_database">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      MYSQL_DATABASE: wordpress</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-mysql_user%3A-wo">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      MYSQL_USER: wordpress</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-mysql_password">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      MYSQL_PASSWORD: wordpress</span>`</div><div id="bkmrk-%C2%A0-wordpress%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  wordpress:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-depends_on%3A-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    depends_on:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---db">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - db</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-image%3A-wordpress">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    image: wordpress</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-ports%3A-7">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    ports:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---%228000%3A80%22">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - "8000:80"</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-restart%3A-always-2">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    restart: always</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-environment%3A-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    environment:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-wordpress_db_h">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      WORDPRESS_DB_HOST: db:3306</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-wordpress_db_u">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      WORDPRESS_DB_USER: wordpress</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-wordpress_db_p">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      WORDPRESS_DB_PASSWORD: wordpress</span>`</div><div id="bkmrk-volumes%3A-3">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">volumes:</span>`</div><div id="bkmrk-%C2%A0-db_data%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  db_data:</span>`</div><div id="bkmrk-%C2%A0-51">` `</div># <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">![ping pong](https://paper.dropboxstatic.com/static/img/ace/emoji/1f3d3.png?version=6.0.0 "ping pong")</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> Docker X Node.js實作：打造前後端具備的 To-Do List</span>

## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">建立Express + Nginx組合</span>

<div id="bkmrk-https%3A%2F%2Fgithub.com%2Fm-6"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://github.com/macchiang/express-nginx","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://github.com/macchiang/express-nginx">[https://github.com/macchiang/express-nginx](https://github.com/macchiang/express-nginx)  
</span></span></div><div id="bkmrk-nginx-config"><span class="ace-all-bold-hthree"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">**Nginx config**</span></span></div><div id="bkmrk-server-%7B">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">server {</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-listen-80-defaul">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    listen 80 default_server;</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-listen-%5B%3A%3A%5D%3A80-d">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    listen [::]:80 default_server;</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-server_tokens-of">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    server_tokens off;</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-client_max_body_">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    client_max_body_size 15M;</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-if-%28%24http_x_forw">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    if ($http_x_forwarded_proto = "http") {</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-return-301-h">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        return 301 https://$host$request_uri;</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%7D">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    }</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-location-%2F-%7B">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    location / {</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-proxy_set_he">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        proxy_set_header X-Real-IP $remote_addr;</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-proxy_set_he-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-proxy_set_he-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        proxy_set_header Host $http_host;</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-proxy_set_he-2">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        proxy_set_header X-NginX-Proxy true;</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-proxy_pass-h">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        </span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><strong>proxy_pass http://node:3000;</strong></span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-%C2%A0-proxy_redire">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">        proxy_redirect off;</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%7D-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    }</span>`</div><div id="bkmrk-%7D-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">}</span>`</div><div id="bkmrk--96"></div><div id="bkmrk-docker-compose.yml-1"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker-compose.yml</span></div><div id="bkmrk-version%3A-%223%22-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">version: "3"</span>`</div><div id="bkmrk-services%3A-8">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">services:</span>`</div><div id="bkmrk-%C2%A0-nginx%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  nginx:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-image%3A-nginx%3Aalp-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    image: nginx:alpine</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-volumes%3A-5">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    volumes:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---.%2Fnginx%3A%2Fetc">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - ./nginx:/etc/nginx/conf.d</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-links%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    links:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---node">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - node</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-ports%3A-8">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    ports:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---80%3A80-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - 80:80</span>`</div><div id="bkmrk-%C2%A0-node%3A">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  node:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-build%3A-2">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    build:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-context%3A-.-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      context: .</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-image%3A-express-n">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    image: express-nginx:latest</span>`</div><div id="bkmrk-%C2%A0-52">` `</div>## <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">打造To-Do List</span>

<div id="bkmrk-https%3A%2F%2Fgithub.com%2Fm-7"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://github.com/macchiang/express-mongoDB-TodoList","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://github.com/macchiang/express-mongoDB-TodoList">[https://github.com/macchiang/express-mongoDB-TodoList](https://github.com/macchiang/express-mongoDB-TodoList)  
</span></span></div><div id="bkmrk--97"></div><div id="bkmrk-ddockerfile"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">Ddockerfile</span></div><div id="bkmrk-%23-base-image-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># Base image</span>`</div><div id="bkmrk-from-node%3A16-alpine-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">FROM node:16-alpine</span>`</div><div id="bkmrk-run-apk-upgrade---no-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN apk upgrade --no-cache</span>`</div><div id="bkmrk-%23-create-app-directo-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># Create app directory</span>`</div><div id="bkmrk-run-mkdir--p-%2Fapp-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN mkdir -p /app</span>`</div><div id="bkmrk-workdir-%2Fapp-3">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">WORKDIR /app</span>`</div><div id="bkmrk-%23-install-app-depend-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># Install app dependencies</span>`</div><div id="bkmrk-copy-package.json-pa-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">COPY package.json package-lock.json ./</span>`</div><div id="bkmrk-run-npm-install-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">RUN npm install</span>`</div><div id="bkmrk-%23-copy-app-source-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># copy app source</span>`</div><div id="bkmrk-copy-.-%2Fapp-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">COPY . /app</span>`</div><div id="bkmrk-%23-service-port-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"># service port</span>`</div><div id="bkmrk-expose-3000-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">EXPOSE 3000</span>`</div><div id="bkmrk-cmd-%5B-%22node%22%2C-%22app.j">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">CMD [ "node", "app.js"]</span>`</div><div id="bkmrk--98"></div><div id="bkmrk-docker-compose.yml-2"><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">docker-compose.yml</span></div><div id="bkmrk-version%3A-%223%22-2">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">version: "3"</span>`</div><div id="bkmrk-services%3A-9">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">services:</span>`</div><div id="bkmrk-%C2%A0-nginx%3A-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  nginx:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-image%3A-nginx%3Aalp-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    image: nginx:alpine</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-volumes%3A-6">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    volumes:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---.%2Fnginx%3A%2Fetc-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - ./nginx:/etc/nginx/conf.d</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-links%3A-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    links:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---node-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - node</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-ports%3A-9">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    ports:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---80%3A80-1">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - 80:80</span>`</div><div id="bkmrk-%C2%A0-node%3A-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  node:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-build%3A-3">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    build:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0-context%3A-.-2">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      context: .</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-depends_on%3A-2">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    depends_on:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---mongo-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - mongo</span>`</div><div id="bkmrk-%C2%A0-mongo%3A-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">  mongo:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-image%3A-mongo%3A5-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    image: mongo:5</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-ports%3A-10">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    ports:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---27017%3A27017-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">      - 27017:27017</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-volumes%3A-7">`    <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">volumes:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-%C2%A0---mongo_data%3A%2F">`      <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">- mongo_data:/data/db</span>`</div><div id="bkmrk-volumes%3A-4">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">volumes:</span>`</div><div id="bkmrk-%C2%A0-%C2%A0-mongo_data%3A-0">`<span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">    mongo_data:</span>`</div># <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">![open book](https://paper.dropboxstatic.com/static/img/ace/emoji/1f4d6.png?version=6.0.0 "open book")</span><span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"> 參考資料</span>

- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z url">[https://blog.gtwang.org/linux/docker-commands-and-container-management-tutorial/](https://blog.gtwang.org/linux/docker-commands-and-container-management-tutorial/)</span>
- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z url">[https://hub.docker.com/r/broadtech/alpine-apache2/dockerfile](https://hub.docker.com/r/broadtech/alpine-apache2/dockerfile)</span>
- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z url">[https://ithelp.ithome.com.tw/articles/10251549](https://ithelp.ithome.com.tw/articles/10251549)</span>
- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z url">[https://www.tpisoftware.com/tpu/articleDetails/1216](https://www.tpisoftware.com/tpu/articleDetails/1216)</span>
- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z url">[https://yeasy.gitbook.io/docker\_practice](https://yeasy.gitbook.io/docker_practice)</span>
- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z url">[https://blog.techbridge.cc/2018/09/07/docker-compose-tutorial-intro/](https://blog.techbridge.cc/2018/09/07/docker-compose-tutorial-intro/)</span>
- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z url">[https://yeasy.gitbook.io/docker\_practice/compose/usage](https://yeasy.gitbook.io/docker_practice/compose/usage)</span>
- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z url">[https://dev.to/truthseekers/setup-a-basic-local-php-development-environment-in-docker-kod](https://dev.to/truthseekers/setup-a-basic-local-php-development-environment-in-docker-kod)</span>
- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z url">[https://dotblogs.com.tw/explooosion/2018/09/15/194754#2](https://dotblogs.com.tw/explooosion/2018/09/15/194754#2)</span>
- <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z url">[https://dca.gitbooks.io/nodejs-tw-wiki-book/content/book/todo\_list/todo\_list.html](https://dca.gitbooks.io/nodejs-tw-wiki-book/content/book/todo_list/todo_list.html)</span>

<div id="bkmrk--99"></div># <span class=" author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z">推薦學習</span>

<div id="bkmrk-https%3A%2F%2Fdev.to%2Fankit"><span class="gallery author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3ls0z82zz79zswnpuz79zedberz68za3xz84zz76zqmz83zmz84z1z75z"><span class="gallery-wrapper" data-ace-gallery-clipboard-json="{"items":[{"type":"unfurl","url":"https://dev.to/ankit01oss/7-github-projects-to-supercharge-your-docker-practices-2i80","serializedZonesForClipboard":{"captionZone":{"atext":{"text":"\n","attribs":"|1+1"},"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0,"dirty":false,"firstDirtyNum":null}}}}]}" data-ace-gallery-urls="https://dev.to/ankit01oss/7-github-projects-to-supercharge-your-docker-practices-2i80">[https://dev.to/ankit01oss/7-github-projects-to-supercharge-your-docker-practices-2i80](https://dev.to/ankit01oss/7-github-projects-to-supercharge-your-docker-practices-2i80)  
</span></span></div>

# 【上課筆記】【udemy】 Docker容器技术从入门到精通

### 上課講義

- [ https://www.docker.tips](https://www.docker.tips " https://www.docker.tips")

---

### 原始碼

- [Oh My Docker · GitHub](https://github.com/oh-my-docker)
- [GitHub - dockersamples/example-voting-app: Example Docker Compose app](https://github.com/dockersamples/example-voting-app)

# 【Demo】Python Flask + Redis 練習

### 準備一個python文件 app.py

```Python
from flask import Flask
from redis import Redis
import os
import socket

app = Flask(__name__)
redis = Redis(host=os.environ.get('REDIS_HOST', '127.0.0.1'), port=6379)


@app.route('/')
def hello():
    redis.incr('hits')
    return f"Hello Container World! I have been seen {redis.get('hits').decode('utf-8')} times and my hostname is {socket.gethostname()}.\n"
```

### 準備一個Dockerfile

```INI
FROM python:3.9.5-slim

RUN pip install flask redis && \
    groupadd -r flask && useradd -r -g flask flask && \
    mkdir /src && \
    chown -R flask:flask /src

USER flask

COPY app.py /src/app.py

WORKDIR /src

ENV FLASK_APP=app.py REDIS_HOST=redis

EXPOSE 5000

CMD ["flask", "run", "-h", "0.0.0.0"]
```

### 準備一個Redis image

###  

```shell
$ docker image pull redis
$ docker image build -t flask-demo .
$ docker image ls
REPOSITORY   TAG          IMAGE ID       CREATED              SIZE
flask-demo   latest       4778411a24c5   About a minute ago   126MB
python       3.9.5-slim   c71955050276   8 days ago           115MB
redis        latest       08502081bff6   2 weeks ago          105MB
```

### 建立一Bridage

```shell
$ docker network create -d bridge demo-network
8005f4348c44ffe3cdcbbda165beea2b0cb520179d3745b24e8f9e05a3e6456d
$ docker network ls
NETWORK ID     NAME           DRIVER    SCOPE
2a464c0b8ec7   bridge         bridge    local
8005f4348c44   demo-network   bridge    local
80b63f711a37   host           host      local
fae746a75be1   none           null      local
```

### 創建container

創建一個叫 `<span class="pre">redis-server</span>` 的container，連到 demo-network上

```shell
$ docker container run -d --name redis-server --network demo-network redis
002800c265020310231d689e6fd35bc084a0fa015e8b0a3174aa2c5e29824c0e
$ docker container ls
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS      NAMES
002800c26502   redis     "docker-entrypoint.s…"   4 seconds ago   Up 3 seconds   6379/tcp   redis-server

```

創建 flask container，連到 demo-network上

```shell
$ docker container run -d --network demo-network \
--name flask-demo \
--env REDIS_HOST=redis-server \
-p 5000:5000 flask-demo

```

打開瀏覽器訪問 [http://127.0.0.1:5000](http://127.0.0.1:5000/)

應該能看到類似下面的內容，每次刷新頁面，計數加1

Hello Container World! I have been seen 36 times and my hostname is 925ecb8d111a.

合併步驟成一腳本

```shell
# prepare image
docker image pull redis
docker image build -t flask-demo .

# create network
docker network create -d bridge demo-network

# create container
docker container run -d --name redis-server --network demo-network redis
docker container run -d --network demo-network --name flask-demo --env REDIS_HOST=redis-server -p 5000:5000 flask-demo

```

# 【Docker】【錯誤處理】無法停止服務

\*使用systemctl stop docker 無法停止服務

```shell
[root@localhost docker]# systemctl stop docker 
Warning: Stopping docker.service, but it can still be activated by:
  docker.socket

```

\*解決

```shell
#停止
[root@localhost docker]# systemctl stop docker.socket
[root@localhost docker]# systemctl stop docker.service

#啟動
[root@localhost docker]# systemctl daemon-reload
[root@localhost docker]# systemctl restart docker

```

\*\*如果還沒辦法解決

```shell
# 删除 docker socket 文件
cd /lib/systemd/system/  &&  rm -rf docker.socket
# 查看 docker 状态，已dead
sudo systemctl status docker.service
# 重新创建 docker.socket
cd /lib/systemd/system/  
vim docker.socket
# 添加如下内容
[Unit]
Description=Docker Socket for the API
PartOf=docker.service

[Socket]
ListenStream=/var/run/docker.sock
SocketMode=0660
SocketUser=root
SocketGroup=docker

[Install]
WantedBy=sockets.target
```

```shell
#啟動
[root@localhost docker]# systemctl daemon-reload
[root@localhost docker]# systemctl restart docker

```

# 【Webmin】相關

 查詢被Deny 需要從Zone default -&gt; Allow queries form ... -&gt; 【 Listed..】-&gt; 加入 any -&gt; 右上角 【 Apply 】

[![image-1681888539338.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-04/scaled-1680-/ekv5h0IjPh5QuFza-image-1681888539338.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-04/ekv5h0IjPh5QuFza-image-1681888539338.png)

[![image-1681888508758.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-04/scaled-1680-/3i1aqB3ElPohVONm-image-1681888508758.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-04/3i1aqB3ElPohVONm-image-1681888508758.png)

[![image-1681888698503.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-04/scaled-1680-/wxlJrQurZrfPxJgj-image-1681888698503.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-04/wxlJrQurZrfPxJgj-image-1681888698503.png)

# Docker 各系統安裝

### 各系統安裝連結

- zabbix  
    [https://www.zabbix.com/documentation/4.4/zh/manual/installation/containers](https://www.zabbix.com/documentation/4.4/zh/manual/installation/containers)  
    [https://github.com/zabbix/zabbix-docker/tree/6.4/Dockerfiles/web-nginx-pgsql](https://github.com/zabbix/zabbix-docker/tree/6.4/Dockerfiles/web-nginx-pgsql)
- elk  
    <div><div>[https://github.com/deviantony/docker-elk](https://github.com/deviantony/docker-elk)  
    https://github.com/deviantony/docker-elk</div></div>
- smokeping  
    [https://cloud.tencent.com/developer/article/1703797](https://cloud.tencent.com/developer/article/1703797)
- jenkins  
    [https://hub.docker.com/\_/jenkins/](https://hub.docker.com/_/jenkins/)
- wekan  
    [https://github.com/wekan/wekan/wiki/Docker](https://github.com/wekan/wekan/wiki/Docker)  
    [https://wekan.github.io/wekan-doc/](https://wekan.github.io/wekan-doc/) (文件)
- postgresSQL  
    [https://hub.docker.com/\_/postgres](https://hub.docker.com/_/postgres)  
    [https://cde566.medium.com/docker-compose%E5%89%B5%E5%BB%BApostgresql-7f3f9519fa20](https://cde566.medium.com/docker-compose%E5%89%B5%E5%BB%BApostgresql-7f3f9519fa20)
- powerdns  
    [https://blog.csdn.net/sdhzdtwhm/article/details/135910057](https://blog.csdn.net/sdhzdtwhm/article/details/135910057)

---

###  

### Jenkins

[Jenkins - Official Image | Docker Hub](https://hub.docker.com/_/jenkins)

[jenkins/jenkins - Docker Image | Docker Hub](https://hub.docker.com/r/jenkins/jenkins)

```shell
docker run -d	\
	-p 8080:8080 -p 50000:50000 \
	-v ~/jenkins/home:/var/jenkins_home \
    --name jenkins \
    jenkins/jenkins:lts
```

ps. jenkins 抓取一定要帶版號不然會報錯

[jenkins Tags | Docker Hub](https://hub.docker.com/_/jenkins?tab=tags)

```
Using default tag: latest
Error response from daemon: manifest for jenkins:latest not found: manifest unknown: manifest unknown
```

```shell
docker pull jenkins/jenkins:lts
```

ps. 要安裝軟體需要使用root 登入

```shell
docker exec -u root -it jenkins sh
```

```
touch: cannot touch ‘/var/jenkins_home/copy_reference_file.log’: Permission denied
Can not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?
```

```shell
sudo chown -R 1000:1000 {mount jenkins home path}
```

ps.使用nginx revers proxy 現設定問題，需要設定 proxy\_set\_header

[Reverse proxy configuration (jenkins.io)](https://www.jenkins.io/doc/book/system-administration/reverse-proxy-configuration-with-jenkins/)

```
server {
    listen       80;
    server_name  tomcat.srou.com;

    location / {
         sendfile off;
        proxy_pass   http://192.168.100.4:10031/;
        proxy_redirect     default;
        proxy_http_version 1.1;

        # Required for Jenkins websocket agents
        proxy_set_header   Upgrade           $http_upgrade;

        proxy_set_header   Host              $host;
        proxy_set_header   X-Real-IP         $remote_addr;
        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_max_temp_file_size 0;

        #this is the maximum upload size
        client_max_body_size       10m;
        client_body_buffer_size    128k;

        proxy_connect_timeout      90;
        proxy_send_timeout         90;
        proxy_read_timeout         90;
        proxy_buffering            off;
        proxy_request_buffering    off; # Required for HTTP CLI commands
        proxy_set_header Connection ""; # Clear for keepalive
      
        
    }

}

```

---

### gitlab

```shell
docker run -d \
	--name gitlab \
	-p 8080:80 -p 443:443 -p 22:22 \
    --privileged \
    --restart always \
    -v ~/gitlab/config:/etc/gitlab \
    -v ~/gitlab/logs:/var/log/gitlab \
    -v ~/gitlab/data:/var/opt/gitlab  \
    gitlab/gitlab-ce
 
```

```
-d : 讓容器(Container)在背景執行
--name : 指定容器(Container)的名稱
-p : 指定主端(Host)對應客端(Quest)的連接埠
--privileged : 容器(Container)内的 root 拥有真正的 root 权限
-restart : 設定為 restart，則容器在異常停止後，會自動重新起動
-v : 將容器(Container)內需永續保存的目錄，連結至主端的目錄
```

 docker-compose.yml

```shell
docker-compose up -d
```

```YAML
version: "3.6"
services:
  gitlab:
    image: gitlab/gitlab-ce:latest
    restart: always
    privileged: true
    ports:
      - '8080:80'
      - '443:443'
      - '22:22'
    volumes:
      - '~/gitlab/data:/var/opt/gitlab'
      - '~/gitlab/logs:/var/log/gitlab'
      - '~/gitlab/config:/etc/gitlab'
```

---

### Tomcat

[Docker部署tomcat出現404錯誤 - 台部落 (twblogs.net)](https://www.twblogs.net/a/5e90b821bd9eee34209011b0)

新版tomcat 不提供manager ，需手動複製，webapps.dist copy -&gt; webapps

---

### PostgresSQL

```YAML
version: "3.7"
services:
    db:
        image: postgres:13.2-alpine
        restart: always
        environment:
            POSTGRES_DB: postgres
            POSTGRES_USER: $USER_NAME #postgres
            POSTGRES_PASSWORD: $PASSWORD #1234
            PGDATA: /var/lib/postgresql/data
        volumes:
            - db-data:/var/lib/postgresql/data
        ports:
            - "5432:5432"
    pgadmin:
        image: dpage/pgadmin4:latest
        restart: always
        environment:
            PGADMIN_DEFAULT_EMAIL: $USER_EMAIL #xxx@gmail
            PGADMIN_DEFAULT_PASSWORD: $PASSWORD #abcd
            PGADMIN_LISTEN_PORT: 80
        ports:
            - "8080:80"
        volumes:
            - pgadmin-data:/var/lib/pgadmin
        links:
            - "db:pgsql-server"
volumes:
    db-data:
    pgadmin-data:
```

# Docker 安全性

[為何在 Docker 中執行特權容器不是個好主意? – 資安趨勢部落格 (trendmicro.com.tw)](https://blog.trendmicro.com.tw/?p=62986)

# Docker 相關連結

#### docker 測試環境(play with docker)

[Play with Docker (play-with-docker.com)](https://labs.play-with-docker.com/)

---

##### 【上課講義】【udemy】 Docker容器技术从入门到精通

- [https://www.docker.tips](https://www.docker.tips/)
- [Vagrant入门系列 - YouTube](https://www.youtube.com/playlist?list=PLfQqWeOCIH4B6YAEXMr6cx4AfnKNBLbZO)

##### 【上課講義】【hiskio】Docker 完全實作！結合 3 大語言掌握容器實務應用

[Docker 完全實作 – Dropbox Paper](https://paper.dropbox.com/doc/Docker--BaNWxS3HDfry~X6QJb7Ze4plAg-pcGbQoyLtmiPPoIlkkK2r)

##### 【鐵人賽】用30天來介紹和使用 Docker

[用30天來介紹和使用 Docker :: 2018 iT 邦幫忙鐵人賽 (ithome.com.tw)](https://ithelp.ithome.com.tw/users/20103456/ironman/1320?page=1)

##### Docker File 官方說明

[Dockerfile reference | Docker Documentation](https://docs.docker.com/engine/reference/builder/)

##### Spring Boot with Docker

[Getting Started | Spring Boot with Docker](https://spring.io/guides/gs/spring-boot-docker/)

##### Docker 從入門到實踐

[前言 - 《Docker —— 從入門到實踐­》正體中文版 (gitbook.io)](https://philipzheng.gitbook.io/docker_practice/)

##### Docker 與 JAVA開發實戰-程序员欣宸

[https://github.com/zq2599/blog\_demos](https://github.com/zq2599/blog_demos)

---

### 各系統安裝連結

- zabbix  
    [https://www.zabbix.com/documentation/4.4/zh/manual/installation/containers](https://www.zabbix.com/documentation/4.4/zh/manual/installation/containers)  
    [https://github.com/zabbix/zabbix-docker/tree/6.4/Dockerfiles/web-nginx-pgsql](https://github.com/zabbix/zabbix-docker/tree/6.4/Dockerfiles/web-nginx-pgsql)
- elk  
    <div><div>[https://github.com/deviantony/docker-elk](https://github.com/deviantony/docker-elk)</div></div>
- smokeping  
    [https://cloud.tencent.com/developer/article/1703797](https://cloud.tencent.com/developer/article/1703797)
- jenkins  
    [https://hub.docker.com/\_/jenkins/](https://hub.docker.com/_/jenkins/)

# Docker 部署IIS

- [\[Docker\] 容器初體驗 - 搞個 IIS Container 來部署網站 | 搞搞就懂 - 點部落 (dotblogs.com.tw)](https://dotblogs.com.tw/wasichris/2020/02/12/164746)
- [將 ASP.NET MVC 應用程式遷移到 Windows 容器 | Microsoft Learn](https://learn.microsoft.com/zh-tw/aspnet/mvc/overview/deployment/docker-aspnetmvc)
- [準備 Windows 作業系統容器 | Microsoft Learn](https://learn.microsoft.com/zh-tw/virtualization/windowscontainers/quick-start/set-up-environment?tabs=Windows-Server#prerequisites)

# Docker 監控相關

#### cadvisor 監控主機狀況

```shell
# 使用cadvisor
$ docker run -d --restart=always -v /var/run:/var/run:rw -p 8080:8080 --name=cadvisor google/cadvisor:latest
```

#### netdata 監控主機狀況

[Install Netdata with Docker | Learn Netdata](https://learn.netdata.cloud/docs/agent/packaging/docker)

```shell
docker run -d --name=netdata \
  -p 19999:19999 \
  -v netdataconfig:/etc/netdata \
  -v netdatalib:/var/lib/netdata \
  -v netdatacache:/var/cache/netdata \
  -v /etc/passwd:/host/etc/passwd:ro \
  -v /etc/group:/host/etc/group:ro \
  -v /proc:/host/proc:ro \
  -v /sys:/host/sys:ro \
  -v /etc/os-release:/host/etc/os-release:ro \
  --restart unless-stopped \
  --cap-add SYS_PTRACE \
  --security-opt apparmor=unconfined \
  netdata/netdata
```

# Docker 儲存

[![image-1675737589104.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-02/scaled-1680-/bAEvwM8s8ZONGIss-image-1675737589104.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-02/bAEvwM8s8ZONGIss-image-1675737589104.png)

### **--mount 與 -v/ --volume**

當在使用 docker create 或是 docker run 的指令時，會有兩個選項可以使用，個別是 --mount 以及 -v (或是 --volume)。

<span class="s1">注意，千萬不要誤以為使用</span> -v <span class="s1">就是用</span> Volume <span class="s1">的方式或者是</span> --mount <span class="s1">就是使用</span> Bind Mount <span class="s1">的方式，不是這樣的！！我們先來看看這兩個在</span> docker create <span class="s1">與</span> docker run <span class="s1">中的說明是甚麼：</span>

```p2
-v, --volume list      Bind mount a volume
    --mount mount      Attach a filesystem mount to the container
```

但其實是可以用 --mount 或 -v 來綁定資料到 Volume，也可以用 --mount 或 -v 來綁定資料到 Filesystem。既然都可以，那為什麼要分兩種呢？

這是因為<span class="s1"> --mount </span>其實出現的比較早，但他設定綁定資料的方式比較完整卻麻煩，因此在後續又出現了<span class="s1"> -v </span>的選項來簡化綁定的設定過程。

---

### **Data Volume**

相關指令

```shell
# 建立volume 
# docker volume create [OPTIONS] [VOLUME]
$docker volume create my-vol

#使用 -v
$ docker run -d --name my-http -v my-vol:/usr/local/apache2 -p 8080:80 httpd
#使用 --mount
$ docker run -d --name my-http --mount source=my-vol,target=/usr/local/apache2 -p 8080:80 httpd
```

```shell
# 列出所有volume
[tomcat@docker]$ docker volume ls
DRIVER              VOLUME NAME
local               2db2d2b1a6ceb9f578d2149dc44625e1996dcc33de0639f8644ce2082182b9a8
local     			my-vol

# 查詢某volume細節
[tomcat@docke]$ docker volume inspect 2db2d2b1a6ceb9f578d2149dc44625e1996dcc33de0639f8644ce2082182b9a8
[
    {
        "CreatedAt": "2019-06-04T15:40:43+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/2db2d2b1a6ceb9f578d2149dc44625e1996dcc33de0639f8644ce2082182b9a8/_data",
        "Name": "2db2d2b1a6ceb9f578d2149dc44625e1996dcc33de0639f8644ce2082182b9a8",
        "Options": null,
        "Scope": "local"
    }
]

[tomcat@docke]$ docker volume inspect my-vol
[
    {
        "CreatedAt": "2023-02-07T02:11:18Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
        "Name": "my-vol",
        "Options": {},
        "Scope": "local"
    }
]


# 刪除所有不使用的volume
[tomcat@docker]$ docker volume prune
WARNING! This will remove all local volumes not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Volumes:
2db2d2b1a6ceb9f578d2149dc44625e1996dcc33de0639f8644ce2082182b9a8
44d6286d1a793ac8c788b0fd364fa8d9430ce3ec079731dbe894dd996cae9adb
925559f4c2399c0c989d8602ca77fb3243dfb83022fe029b09b130c9521e7f89
9484acbd73c53af47f29add893c5125b0cd7f0f96e521c54e643f1aeb8f97eeb
b47202da45060de8f67e48058966b32078d525cadd1c94671a0ffe33f3d5df4b
006d620ae77bf7530ea37abd48b9c20ac264956f4b9ef3397e2c5072f32677c2
15e0fd2e575c2c24a2a137ff420dbf30326f3859d483561efc45e568d294c802
98b5385b04a401e9e870c5a1e807d69c56a1764b2ec91baedd9c13994cb2c863
ffdae0e84d00966afdc0df55d664d06ee99c14d249ad7558848088c464d472ff

Total reclaimed space: 77.8MB

# 指定volume mount 至 /var/www/html
[tomcat@docker]$ docker run -d -v html_doc:/var/www/html httpd



```

---

### **Bind Mount to FileSystem**

```shell
# 直接mount 外部資料夾 至 /var/www/html
# 相對路徑
[tomcat@docker-qc html-doc]$ docker run -d -v ${PWD}/html-doc:/var/www/html httpd
# 絕對路徑
[tomcat@docker-qc html-doc]$ docker run -d -v /home/tomcat/docker/html-doc:/var/www/html httpd
```

### **Bind Mount to Memory**

Tmpfs，全名 Temporary File System，是在 Linux 暫存檔儲存空間的常見名稱，通常以掛載檔案系統方式實現。他可以將資料儲存在記憶體中而不是存在硬碟內，所以只要一重新開機，資料則會被抹除。

Tmpfs <span class="s1">如其名，如果使用</span> Tmpfs Mount <span class="s1">則資料會存在於記憶體上，在重啟</span> Container <span class="s1">的時候就會</span> Reset <span class="s1">資料。</span>

<span style="background-color: #ffff99;"><span class="s1">特別注意的是，使用</span> Tmpfs Mount <span class="s1">並不是使用</span> -v <span class="s1">的選項，而是使用</span> --tmpfs <span class="s1">，</span>--tmpfs <span class="s1">就如同</span> -v<span class="s1">一樣簡化了使用</span> --mount <span class="s1">時所需的設定。</span></span>

```p1
使用 --tmpfs
$ docker run -d --name my-http --tmpfs /usr/local/apache2/htdocs -p 8080:80 httpd
使用 --mount
$ docker run -d --name my-http --mount type=tmpfs,destination=/usr/local/apache2/htdocs -p 8080:80 httpd
```

---

[![image-1642495777057.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/scaled-1680-/ucGrtB4qipR1RYnh-image-1642495777057.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/ucGrtB4qipR1RYnh-image-1642495777057.png)

官方参考链接 [https://docs.docker.com/storage/volumes/#share-data-among-machines](https://docs.docker.com/storage/volumes/#share-data-among-machines)

---

### 常用volume mapping (log)

```shell
# nginx
docker run -p 80:80 -itd -v /tmp/log/nginx/docker-nginx-log:/var/log/nginx nginx

```

### **使用綁定的不同時機比較**

既然 Docker 提供了這麼多的方式讓我們儲存資料，那麼我們應該在甚麼情況下使用哪一種方式來儲存呢？

#### **- 使用 Volume 的時機**

- 想要在多個 Container 中共用資料的時候
- 在無法確保存放資料路徑或結構的時候
- 當 Docker 不在本機端而是在雲端或遠端的時候
- 當有需要資料的備份、還原、轉移的時候
- 當資料有大量 I/O 的情況，Docker 建議你可以把資料放在 Docker VM 內，會有更好的性能表現
- 當你的應用程式需要完整且原生的 File System 的時候

#### **- 使用 Bind Mount 的時機**

- 想要將 Config 設定檔同時分享給 Container 與本機端的時候
- 想要同時分享程式碼或其他來源文件給 Container 與本機端的時候
- 當非常確定所綁定的路徑底下，其資料夾與結構等等都非常確定且保持一致不會改變的時候

#### **- 使用 Tmpfs Mount 的時機**

- 不想讓資料被永久儲存的時候
- 需要每次重啟都有乾淨的環境時
- 當想要讀寫性能非常好時，因為它掛載的空間是在記憶體上

[Docker筆記 - 讓資料遠離Container，使用 Volume、Bind Mount 與 Tmpfs Mount](https://medium.com/alberthg-docker-notes/docker%E7%AD%86%E8%A8%98-%E8%AE%93%E8%B3%87%E6%96%99%E9%81%A0%E9%9B%A2container-%E4%BD%BF%E7%94%A8-volume-bind-mount-%E8%88%87-tmpfs-mount-6908da341d11 "Docker筆記 - 讓資料遠離Container，使用 Volume、Bind Mount 與 Tmpfs Mount")

# Docker Compose

### 安裝

[Install Docker Compose | Docker Documentation](https://docs.docker.com/compose/install/)

```shell
# linux
# 到這裡找版號 https://github.com/docker/compose/releases
# sudo curl -L "https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-linux-x86_64" -o /usr/local/bin/docker-compose
$ sudo curl -L "https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
$ docker-compose --version
docker-compose version 1.29.2, build 5becea4c

# 找不到命令的可以建立軟連結
$ sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
```

熟悉python的朋友，可以使用pip去安装docker-Compose

```
$ pip install docker-compose
```

---

### YML 語法結構

[compose-spec/spec.md at master · compose-spec/compose-spec · GitHub](https://github.com/compose-spec/compose-spec/blob/master/spec.md#volumes-top-level-element)

```YAML
version: "3.8"

services: # 容器
  servicename: # 服務名字，這個名字也是內部 bridge網絡可以使用的 DNS name
  	container_name: #自定義container 名稱，不使用會用 servicename
    # build: ./buildfolder
    build: # 加此參數會使用docker file 建立
    # 如果要指定 dockerfile 使用 context + dockerfile
    #build:
    #  context: ./buildfolder
    #  dockerfile: Dockerfile.dev
    image: # 鏡像的名字(如果使用build，此選項會變成image名稱，如果沒有build，會從網路拉取該名稱image)
    command: # 可選，如果設置，則會覆蓋默認鏡像裡的 CMD命令， ex: /bin/sh -c “while true;do sleep 3600 ;done”
    environment: # 可選，相當於 docker run裡的 --env
    volumes: # 可選，相當於docker run裡的 -v
    networks: # 可選，相當於 docker run裡的 --network
    ports: # 可選，相當於 docker run裡的 -p
    
  servicename2: #第二個服務

volumes: # 可選，相當於 docker volume create

networks: # 可選，相當於 docker network create
```

範例

```YAML
version: "3.8"

services:
  flask-demo:
    image: flask-demo:latest
    environment:
      - REDIS_HOST=redis-server
      - TZ=Asia/Taipei #設定時區
    networks:
      - demo-network
    ports:
      - 8080:5000
    volumes:
      - ./config/logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml:ro,Z 
      - ./config/logstash/pipeline:/usr/share/logstash/pipeline:ro,Z #ro: read only, Z: 在SElinux 下唯讀(啟用SEliunx才有用)

  redis-server:
    image: redis:latest
    networks:
     - demo-network

networks:
  demo-network:
```

##### docker-compose 與 docker 版本對應，語法說明

##### [https://docs.docker.com/compose/compose-file/](https://docs.docker.com/compose/compose-file/)

##### docker run 轉換 docker-compose 語法工具

#### [https://www.composerize.com/](https://www.composerize.com/)

### 常用指令

```shell
# 前景執行
docker-compose up
# 背景執行
docker-compose up -d
# 使用自定義前贅字(預設是用資料夾名稱)
docker-compose -p my_project up -d

# 停止docker-compose
docker-compose stop

# 移除docker-compose
docker-compose down
# 移除docker-compose 同時移除volumn
docker-comopose down -v

# 列出container
docker-compose ps 

# 列出log
docker-compose log
docker-compose log -f

# 在webapp 內執行sh
docker-compose exec webapp sh

# 使用top監控
docker-compose top

# 抓取(build)image
docker-compose pull

```

### 更新服務

當程式有異動，想要重新build image

```shell
# 重新build image
docker-compose build
# 啟動時重新build
docker-compose up --build
# 如果有服務被移除，使用 --remove-orphans 移除相關資源
docker-composer up --remove-orphans
# 如果有變更掛載，需要重啟服務
docker-composer restart
```

### 網路配置

```INI
networks:
  mynetwork1:
    ipam:
      driver: default
      config:
        - subnet: 172.28.0.0/16
   mynetwork2:
     ipam:
      driver: default
      config:
        - subnet: 172.29.0.0/16
```

### 清除環境

```shell
docker-compose stop
docker-compose rm 
docker system prund -f
```

[![image-1642476741214.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/scaled-1680-/im4FodphkDPbjQJl-image-1642476741214.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/im4FodphkDPbjQJl-image-1642476741214.png)

docker-compose volume 寫法

[![image-1642476831997.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/scaled-1680-/0giARnkNtAdZy4q4-image-1642476831997.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/0giARnkNtAdZy4q4-image-1642476831997.png)

[![image-1642476893794.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/scaled-1680-/X7edUouET00rOpGG-image-1642476893794.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/X7edUouET00rOpGG-image-1642476893794.png)

docker compose yml 範例

```YAML
version: "3"
services:
  webapp:
    #指定image
    image: httpd:alpine
    ports:
        - "80:80"
    volumes:
        - "/data"
```

```YAML
version: "3"
services:
  webapp:
    #指定image
    build:
	context: .
        dockerfile: Dockerfile-alternate
	args:
		buildno:  1
```

---

###  相關連結

- [Docker 教學課程-第8部分：使用 Docker Compose | Microsoft Docs](https://docs.microsoft.com/zh-tw/visualstudio/docker/tutorials/use-docker-compose)
- [Compose file version 3 reference | Docker Documentation](https://docs.docker.com/compose/compose-file/compose-file-v3/)
- [Overview of docker-compose CLI | Docker Documentation](https://docs.docker.com/compose/reference/)
- [官網文件 https://docs.docker.com/compose/](https://docs.docker.com/compose/)

# Docker container 常用軟體安裝

更新 apt-get

```shell
#更新apt-update
apt-get update
```

#### vim

```shell
sudo apt-get install -y vim
```

#### ip

```shell
sudo apt-get install -y iproute2
```

#### ping

```shell
sudo apt-get install -y iputils-ping
```

#### ifconfig, netestat

```shell
sudo apt-get install -y net-tools
```

##### nc (netcat)

```shell
sudo apt-get install -y netcat
```

#### maven

```shell
FROM jenkins/jenkins:lts
RUN apt-get update && apt-get install -y maven
```

---

### apt-get 使用proxy

```shell

#apt_proxy.conf 設定 proxy
Acquire::http::proxy "http://127.0.0.1:8000/";
Acquire::ftp::proxy "ftp://127.0.0.1:8000/";
Acquire::https::proxy "https://127.0.0.1:8000/";

#可以用“-c”選項來指定使用配置檔案
sudo apt-get -c ~/apt_proxy.conf update
#apt-get也有一個“-o”選項，直接跟apt-get的設定變數，就不用指定配置檔案了，比如
sudo apt-get -o Acquire::http::proxy="http://127.0.0.1:8000/" update

```

# Docker File

### Docker File 官方說明

[Dockerfile reference | Docker Documentation](https://docs.docker.com/engine/reference/builder/)

官方各類型docker file 專案連結(可找到專案git repo)

[official-images/library at master · docker-library/official-images · GitHub](https://github.com/docker-library/official-images/tree/master/library)

[![image-1645294864450.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-02/scaled-1680-/xIH3UQ3k7ZQ3bYys-image-1645294864450.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-02/xIH3UQ3k7ZQ3bYys-image-1645294864450.png)

---

### Build Dockerfile

```shell
# docker image build -t {name}:{tag} {dokerfile 目錄}
docker image build -t hello_py:v1 .
```

### Docker File 結構

```INI
# 基礎鏡像檔
FROM ubuntu:21.04
# 環境變數
ENV VERSION=2.0.1
# 執行命令(可在執行容器的時候使用其他命覆蓋)
RUN apt-get update && \
    apt-get install -y wget && \
    wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linux_amd64.tar.gz && \
    tar zxf ipinfo_${VERSION}_linux_amd64.tar.gz && \
    mv ipinfo_${VERSION}_linux_amd64 /usr/bin/ipinfo && \
    rm -rf ipinfo_${VERSION}_linux_amd64.tar.gz
# 移動到當前目錄
WORKDIR /app
# 複製檔案
COPY hello.py /app/hello.py
# 複製檔案(如果是壓縮檔會自行解壓縮，一般來說還是用COPY)
ADD hello.tar.gz /app/
# 執行命令(無法覆蓋)
ENTRYPOINT ["echo", "hello docker"]
```

# Docker Image 相關

### 鏡像的獲取

- pull from `<span class="pre">registry</span>` (online) 從registry拉取
    
    
    - public（公有）
    - private（私有）
- build from `<span class="pre">Dockerfile</span>` (online) 從Dockerfile構建
- load from `<span class="pre">file</span>` (offline) 文件導入 （離線）

官方image:  
[https://hub.docker.com/search?type=image&amp;image\_filter=official](https://hub.docker.com/search?type=image&image_filter=official)

---

常用 public image server

- [Docker Hub Container Image Library | App Containerization](https://hub.docker.com/)
- [Quay Container Registry · Quay](https://quay.io/)

##### \*建立映像檔

```shell
# 於當前目錄，按Dockerfile.test中的指令，建立test:v1 ({name:tag})的映像檔
docker build . -f Dockrfile.test -t test:v1
```

##### \*拉取與推送映像檔

```shell
# 使用docker hub 下載image ({name:tag})
docker pull node:alpine
# 使用quay.io 下載image 
docker pull quay.io/bitnami/tomcat
# node:alpine 重新命名標籤 username/nodea:alpine
docker tag node:alpine username/nodea:alpine
# 推送至私人倉庫
docker push username/node:alpine
```

---

##### 使用私有registry

```shell
# 修改鏡像registry
sudo vim /etc/docker/daemon.json
#########################################################
#  可與許http鏡像　"insecure-registries" : ["10.60.78.79"],
#  鏡像站 "registry-mirrors": ["http://hub-mirror.c.163.com"]

{
  "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]
}
#########################################################
# 使文件生效
sudo systemctl daemon-reload
# 重啟docker
sudo service docker restart
```

##### 使用proxy 抓取鏡像檔

\*ubuntu

```shell
# 編輯設定檔
vim /etc/default/docker

##檔案內容
export http_proxy="http://192.168.33.10:3128/"
export https_proxy="http://192.168.33.10:3128/"
##

# proxy 如果有帳密驗證
export http_proxy="https://username:password@192.168.33.10:3128/"

# 重啟docker 
systemctl restart docker
```

\*centos

```shell
vim /etc/systemd/system/docker.service.d/http-proxy.conf 

###################################################################################
[Service]
Environment="HTTP_PROXY=http://192.168.1.1:3128/"
Environment="HTTPS_PROXY=http://192.168.1.1:3128/"
#如果要排除不走
Environment="NO_PROXY=localhost,127.0.0.0/8,docker-registry.somecorporation.com"

###################################################################################

#重讀設定
systemctl daemon-reload

#重啟docker
systemctl restart docker

```

---

##### 查看image

```shell
# 取得id
[root@localhost ~]# docker image ls

REPOSITORY   TAG            IMAGE ID       CREATED        SIZE
tomcat       latest         9f35737a8466   30 hours ago   680MB
busybox      latest         beae173ccac6   3 weeks ago    1.24MB
openjdk      8-jre-alpine   f7a292bbb70c   2 years ago    84.9MB
```

```shell
[root@localhost ~]# docker image inspect f7a292bbb70c
[
    {
        "Id": "sha256:f7a292bbb70c4ce57f7704cc03eb09e299de9da19013b084f138154421918cb4",
        "RepoTags": [
            "openjdk:8-jre-alpine"
        ],
        "RepoDigests": [
            "openjdk@sha256:f362b165b870ef129cbe730
.......

# 重要訊息
 "Id": "sha256:f7a292bbb70c4ce57f7704cc03eb09e299de9da19013b084f138154421918cb4",
        "RepoTags": [
            "openjdk:8-jre-alpine"
        ],


"Architecture": "amd64",
 "Os": "linux",

```

##### 刪除 image

```shell
# 查詢要刪除imge id
[root@localhost ~]# docker image ls
REPOSITORY   TAG            IMAGE ID       CREATED        SIZE
tomcat       latest         9f35737a8466   30 hours ago   680MB
busybox      latest         beae173ccac6   3 weeks ago    1.24MB
openjdk      8-jre-alpine   f7a292bbb70c   2 years ago    84.9MB
# 刪除 image
[root@localhost ~]# docker image rm beae173ccac6
Untagged: busybox:latest
Untagged: busybox@sha256:5acba83a746c7608ed544dc1533b87c737a0b0fb730301639a0179f9344b1678
Deleted: sha256:beae173ccac6ad749f76713cf4440fe3d21d1043fe616dfbe30775815d1d0f6a
Deleted: sha256:01fd6df81c8ec7dd24bbbd72342671f41813f992999a3471b9d9cbc44ad88374

# 刪除名字跟nginx 有關的imge (-f 強制)
[root@localhost ~]# docker images | grep nginx | awk '{print $3}' | xargs docker rmi
[root@localhost ~]# docker images | grep nginx | awk '{print $3}' | xargs docker rmi -f

# 刪除沒有名字的image (-f 強制)
[root@localhost ~]# docker images | grep "<none>" | awk '{print $3}' | xargs docker rmi
[root@localhost ~]# docker images | grep "<none>" | awk '{print $3}' | xargs docker rmi -f
```

##### image 匯入匯出

```shell
# 查看 image
[root@localhost ~]# docker image ls
REPOSITORY   TAG            IMAGE ID       CREATED        SIZE
tomcat       latest         9f35737a8466   30 hours ago   680MB
busybox      latest         beae173ccac6   3 weeks ago    1.24MB
openjdk      8-jre-alpine   f7a292bbb70c   2 years ago    84.9MB

# 匯出 image
# docker image save {name}:{tag} -o {匯出檔名}
[root@localhost ~]# docker image save openjdk:8-jre-alpine -o openjdk:8-jre-alpine.image
# 查看是否匯出
[root@localhost ~]# ls openjdk\:8-jre-alpine.image 
openjdk:8-jre-alpine.image

```

```shell
# image 匯入
# docker image load -i {image file}
[root@localhost ~]# docker image load -i ./openjdk\:8-jre-alpine.image 
f1b5933fe4b5: Loading layer [==================================================>]  5.796MB/5.796MB
9b9b7f3d56a0: Loading layer [==================================================>]  3.584kB/3.584kB
edd61588d126: Loading layer [==================================================>]  80.28MB/80.28MB
Loaded image: openjdk:8-jre-alpine

```

### push image to docker hub

```shell
# 建立上傳image
#docker image tag nginx {docker hub id}/{name}:{tag}
docker image tag nginx treemanou/my_nginx:v2
#docker image push {docker hub id}/{name}:{tag}
docker image push treemanou/my_nginx:v2
```

### commit 建立 image

```shell
# 找一個conatiner
$ docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED          STATUS                      PORTS     NAMES
e09bb406dcef   busybox   "sh"      53 seconds ago   Exited (0) 18 seconds ago             hardcore_bartik
# 利用此container建立 image
# docker commit {container id} {name}:{tag}
$ docker commit e09 treemanou/my_busybox:v3
sha256:42bc77669110af56336ec10afdbd7f92fa371f02ecccbda1e0bbb1bbc00f3cf6
```

### Docker File

---

##### Docker File 官方說明

[Dockerfile reference | Docker Documentation](https://docs.docker.com/engine/reference/builder/)

---

\*ENV 環境變數

```
ENV ANT_HOME /opt/ant
```

\*ARG傳入參數

```
FROM　alpine
ARG NODE_ARG
ENV NODE_ENV="${NODE_ARG:-development}"
RUN echo "ARG=${NODE_ARG}, ENV=${NODE_ENV}"
CMD ehco "ARG=${NODE_ARG}, ENV=${NODE_ENV}"
```

```shell
# 傳入NODE_ARG
# DOCKER_BUILDKIT=0
DOCKER_BUILDKIT=0 docker build --build-arg NODE_ARG=staging　.
```

\*工作目錄 WORKDIR

```shell
# /  下
RUN echo "say hi" > hi.txt
WORKDIR /app
#  /app 下
RUN echo "hello" > world.txt
```

\*指定用戶USER (不指定舊識)

```shell
# 新增 group與user
RUN groupadd -r redis && useradd -r -g redis redis
# 指定當前user為redis
USER redis

# 以redis的身份執行reidis-server
RUN ["redis-server"]
```

\*Health Check

```shell
FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -fs http://localhost/ || exit 1
```

---

```shell
FROM registry.test.com.tw/test/centos:openjdk8_tomcat8.5

ENV CATALINA_HOME /opt/tomcat
ENV SERVICE_HOME /usr/AP/fubon
ENV ANT_HOME /opt/ant
ENV JAVA_HOME /usr/lib/jvm/java-1.8.0-openjdk
ENV PATH="${PATH}:${ANT_HOME}/bin:${CATALINA_HOME}/bin"
ENV AWS_HOME="/root/aws"

RUN wget https://archive.apache.org/dist/ant/binaries/apache-ant-1.10.7-bin.tar.gz \
  && tar -zxvf apache-ant-1.10.7-bin.tar.gz \
  && rm apache-ant-1.10.7-bin.tar.gz \
  && mv apache-ant* ${ANT_HOME} \
  && yum install -y python3 \
  && pip3 install awscli --upgrade --user \
  && ln -s  /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/ /usr/lib/jvm/java-1.8.0-openjdk.x86_64 \
  && yum install -y httpd php php-gd 
  
COPY server.xml ${CATALINA_HOME}/conf/

COPY my_entrypoint.sh /my_entrypoint.sh
RUN chmod -v +x /my_entrypoint.sh

COPY --chown=user:group file* /app/

COPY server_builder.sh ${SERVICE_HOME}/

#Set Alias
RUN echo "alias cdcm='cd /usr/AP/test/cm'" >> ~/.bashrc \
 && echo "alias restartTomcat='cd /opt/tomcat/bin; ./shutdown.sh; /bin/sleep 3; ps -ef | grep '\''org.apache.catalina.startup.Bootstrap start'\'' | grep -v grep | awk '\''{print $2}'\'' | xargs kill; ./startup.sh'" >> ~/.bashrc \
 && echo "alias cmant='cd /usr/AP/test/cm/src; ant; restartTomcat'"  >> ~/.bashrc \
 
EXPOSE 8080

CMD ["-D","FOREGROUND"]

# 預設目錄
WORKDIR /usr/AP/test/ 

#  一定會執行
ENTRYPOINT ["/my_entrypoint.sh"]

```

\* 複寫CMD

```
docker run --rm apache:v1 -v
```

---

多階段建置

```shell
# 建置環境
FROM alpine as build
RUN echo "hello" > mytest

#  執行環境
FROM alpine
COPY --from=build /mytest .
RUN cat /mytest 
```

**Distroless Dock image** =&gt; 精簡image

---

### 常用image

#### busybox

- 提供輕量簡單的shell 的 linux 容器

##### containous/whoami

- 提供返回ip hostname 服務

```shell
vagrant@swarm-manager:~$ curl 192.168.200.10:8080
Hostname: fdf7c1354507
IP: 127.0.0.1
IP: 10.0.0.7
IP: 172.18.0.3
IP: 10.0.1.14
RemoteAddr: 10.0.0.2:36828
GET / HTTP/1.1
Host: 192.168.200.10:8080
User-Agent: curl/7.68.0
Accept: */*
```

# Docker Image Server DockerRegistry

[Day7：建立 private 的 Docker Registry - iT 邦幫忙::一起幫忙解決難題，拯救 IT 人的一天 (ithome.com.tw)](https://ithelp.ithome.com.tw/articles/10191213)

```shell
$ docker run -d -p 5000:5000 -v /home/user1/storage:/var/lib/registry --name registry registry:2

#
# -d：執行的 docker container 是 run 在背景的狀態，所以需要使用 docker logs 的指令才可以看到 log 狀態
# -p：主機的 5000 port mapping 到 container 的 5000 port
# -v：因為 push 到 docker registry 的資料是放在 container 裡面的，如果把 docker container 刪除掉 docker registry 的 Image 資料就會不見，因此需要使用 –v 參數將主機的檔案路徑 mapping 到 container 裡面的檔案路徑，這樣 docker container 被刪除 docker registry 的 Image 資料還會存在
# --name：設定 docker container 的名稱

```

##### clinet / server 都要加 daemon.json 

```shell
# vim /etc/docker daemon.json
# 允許192.168.1.1:5000 (port 一定要寫) 可不走https
{
"insecure-registries":["192.168.1.1:5000"]
}

```

### 上傳 image

```shell
# 假設本機ip 192.168.1.1 (registry server)
# 下載image
docker pull busybox

# 複製image 成 mybusybox
docker push tag busybox 192.168.1.1:5000/mybusybox
# 上傳image 
docker pull 192.168.1.1:5000/mybusybox
```

### 下載 image

```shell
# 假設本機ip 192.168.1.2 (docker client)
docker pull docker pull 192.168.1.1:5000/mybusybox

```

# Docker Image Server nexus

[sonatype/nexus3 - Docker Image | Docker Hub](https://hub.docker.com/r/sonatype/nexus3/)

1. *Use a docker volume*. Since docker volumes are persistent, a volume can be created specifically for this purpose. This is the recommended approach.
    
    ```
    $ docker volume create --name nexus-data
    $ docker run -d -p 8081:8081 --name nexus -v nexus-data:/nexus-data sonatype/nexus3
    ```
2. *Mount a host directory as the volume*. This is not portable, as it relies on the directory existing with correct permissions on the host. However it can be useful in certain situations where this volume needs to be assigned to certain specific underlying storage.
    
    ```
    $ mkdir /some/dir/nexus-data && chown -R 200 /some/dir/nexus-data
    $ docker run -d -p 8081:8081 --name nexus -v /some/dir/nexus-data:/nexus-data sonatype/nexus3
    ```

# Docker Network

[![image-1642500508897.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/scaled-1680-/3BWF5JGV4iFGcRUR-image-1642500508897.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/3BWF5JGV4iFGcRUR-image-1642500508897.png)

[![image-1642576871696.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/scaled-1680-/VnZuBqb1rvcf8IZh-image-1642576871696.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/VnZuBqb1rvcf8IZh-image-1642576871696.png)

建立兩容器

```shell
$ docker container run -d --rm --name box1 busybox /bin/sh -c "while true; do sleep 3600; done"
$ docker container run -d --rm --name box2 busybox /bin/sh -c "while true; do sleep 3600; done"
$ docker container ls
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS     NAMES
4f3303c84e53   busybox   "/bin/sh -c 'while t…"   49 minutes ago   Up 49 minutes             box2
03494b034694   busybox   "/bin/sh -c 'while t…"   49 minutes ago   Up 49 minutes             box1
```

### 容器間的通信-&gt;使用bridge

```shell
$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
1847e179a316   bridge    bridge    local
a647a4ad0b4f   host      host      local
fbd81b56c009   none      null      local
$ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "1847e179a316ee5219c951c2c21cf2c787d431d1ffb3ef621b8f0d1edd197b24",
        "Created": "2021-07-01T15:28:09.265408946Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "03494b034694982fa085cc4052b6c7b8b9c046f9d5f85f30e3a9e716fad20741": {
                "Name": "box1",
                "EndpointID": "072160448becebb7c9c333dce9bbdf7601a92b1d3e7a5820b8b35976cf4fd6ff",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            },
            "4f3303c84e5391ea37db664fd08683b01decdadae636aaa1bfd7bb9669cbd8de": {
                "Name": "box2",
                "EndpointID": "4cf0f635d4273066acd3075ec775e6fa405034f94b88c1bcacdaae847612f2c5",
                "MacAddress": "02:42:ac:11:00:03",
                "IPv4Address": "172.17.0.3/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]
```

"IPAM": {  
 "Driver": "default",  
 "Options": null,  
 "Config": \[  
 {  
 "Subnet": "<span style="color: #ff0000;">172.17.0.0/16</span>",  
 "Gateway": "<span style="color: #ff0000;">172.17.0.1</span>"  
 }  
 \]  
},

"com.docker.network.bridge.name": "docker0",

```shell
# brctl 顯示bridge tool
$ brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.0242759468cf       no              veth8c9bb82
                                                        vethd8f9afb
```

### 容器對外通訊-&gt;iptables nat

```
$ ip route
default via 10.0.2.2 dev eth0 proto dhcp metric 100
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
192.168.200.0/24 dev eth1 proto kernel scope link src 192.168.200.10 metric 101
```

```
$ sudo iptables --list -t nat
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
DOCKER     all  --  anywhere             anywhere             ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
DOCKER     all  --  anywhere            !loopback/8           ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  172.17.0.0/16        anywhere

Chain DOCKER (2 references)
target     prot opt source               destination
RETURN     all  --  anywhere             anywhere
```

[![image-1642576581794.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/scaled-1680-/Bzvyu502k6v1ZIkl-image-1642576581794.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/Bzvyu502k6v1ZIkl-image-1642576581794.png)

docker network create --help

docker network create -d<span style="color: #ff0000;"> {driver}</span> --gateway <span style="color: #ff0000;">{gateway}</span> --subnet<span style="color: #ff0000;"> {subnet} {name}</span>  
docker network create -d bridge --gateway 127.200.0.1 -subnet 172.200.0.0/16 demo

```shell
[tomcat@docker-qc html-doc]$ docker network create -d bridge mybridge
65c5ba6cbea2fa677e8dd0623a510f3461e24ccac196b7374fc5e8d03803ec9e

[tomcat@docker-qc html-doc]$  docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
780162e5fe57        bridge              bridge              local
3c5b59c8a708        host                host                local
65c5ba6cbea2        mybridge            bridge              local

[tomcat@docker-qc html-doc]$ docker network inspect mybridge
[
    {
        "Name": "mybridge",
        "Id": "65c5ba6cbea2fa677e8dd0623a510f3461e24ccac196b7374fc5e8d03803ec9e",
        "Created": "2022-01-18T18:44:39.95740673+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

```

```shell
# 建立連接mybridge的container (--network 不設定會用預設的)
$ docker container run -d --rm --name box3 --network mybridge busybox /bin/sh -c "while true; do sleep 3600; done"
# 既有container 連接mybridge
$ docker network connect mybridge box1
# container 中斷 mybridge
$ docker network disconnect mybridge box1

```

---

### **Network type**

- bridge
- host
- none

**bridge**: 新增一網段，橋接本地網路  
**host**: 直接使用本地網路(無法看到port -&gt; 等於直接開在本機port 80，好處不用nat，壞處只能開一個)

```shell
#docker dontainer run -d --name {container name} --network {network type} {image}
docker dontainer run -d --name web --network host nginx
```

[![image-1642651291388.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/scaled-1680-/YfM2Dcw8nqoK2c7T-image-1642651291388.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/YfM2Dcw8nqoK2c7T-image-1642651291388.png)

只能開一個，多開會報錯

[![image-1642651575691.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/scaled-1680-/BW5D4QWEKWICxDLA-image-1642651575691.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/BW5D4QWEKWICxDLA-image-1642651575691.png)

**none**: 只有 lookback 127.0.0.1 沒有其他網路

---

### 網路命名空間

- ##### bridge tool : <span style="color: #ff0000;">brctl</span> 安裝

```shell
#Ubuntu
apt-get install bridge-utils 
#Centos
sudo yum install bridge-utils
```

[![image-1642659997529.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/scaled-1680-/jrMYPAmozUFUHgTx-image-1642659997529.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/jrMYPAmozUFUHgTx-image-1642659997529.png)

創建bridge

```shell
[vagrant@docker-host1 ~]$ sudo brctl addbr mydocker0
[vagrant@docker-host1 ~]$ brctl show
bridge name     bridge id               STP enabled     interfaces
mydocker0               8000.000000000000       no
[vagrant@docker-host1 ~]$

```

add-ns-to-br.sh

```shell
#!/bin/bash

bridge=$1
namespace=$2
addr=$3

vethA=veth-$namespace
vethB=eth00

sudo ip netns add $namespace
sudo ip link add $vethA type veth peer name $vethB

sudo ip link set $vethB netns $namespace
sudo ip netns exec $namespace ip addr add $addr dev $vethB
sudo ip netns exec $namespace ip link set $vethB up

sudo ip link set $vethA up

sudo brctl addif $bridge $vethA
```

```shell
#run shell
[vagrant@docker-host1 ~]$ sh add-ns-to-br.sh mydocker0 ns1 172.16.1.1/16
[vagrant@docker-host1 ~]$ sh add-ns-to-br.sh mydocker0 ns2 172.16.1.2/16

#up bridge
[vagrant@docker-host1 ~]$ sudo ip link set dev mydocker0 up
```

驗證

```shell
[vagrant@docker-host1 ~]$ sudo ip netns exec ns1 bash
[root@docker-host1 vagrant]# ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
5: eth00@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether f2:59:19:34:73:70 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.16.1.1/16 scope global eth00
    valid_lft forever preferred_lft forever
    inet6 fe80::f059:19ff:fe34:7370/64 scope link
    valid_lft forever preferred_lft forever
[root@docker-host1 vagrant]# ping 172.16.1.2
PING 172.16.1.2 (172.16.1.2) 56(84) bytes of data.
64 bytes from 172.16.1.2: icmp_seq=1 ttl=64 time=0.029 ms
64 bytes from 172.16.1.2: icmp_seq=2 ttl=64 time=0.080 ms
^C
--- 172.16.1.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 0.029/0.054/0.080/0.026 ms
[root@docker-host1 vagrant]#
```

[![image-1642660373795.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/scaled-1680-/J47bjgngTUMyQynn-image-1642660373795.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/J47bjgngTUMyQynn-image-1642660373795.png)

[![image-1642660418946.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/scaled-1680-/96PF3Lseg7J1VaxD-image-1642660418946.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/96PF3Lseg7J1VaxD-image-1642660418946.png)

使用iptables 實現nat  
[NAT with Linux and iptables - Tutorial (Introduction) (karlrupp.net)](https://www.karlrupp.net/en/computer/nat_tutorial)

---

### 使用自定義網路練習

使用自定義網路，預設自帶dns，容器間可相互使用name ping 通

準備python 檔案 <span style="color: #ff0000;">**app.py**</span>

```Python
from flask import Flask
from redis import Redis
import os
import socket

app = Flask(__name__)
redis = Redis(host=os.environ.get('REDIS_HOST', '127.0.0.1'), port=6379)


@app.route('/')
def hello():
    redis.incr('hits')
    return f"Hello Container World! I have been seen {redis.get('hits').decode('utf-8')} times and my hostname is {socket.gethostname()}.\n"
```

準備一個 Dockerfile

```shell
FROM python:3.9.5-slim

RUN pip install flask redis && \
    groupadd -r flask && useradd -r -g flask flask && \
    mkdir /src && \
    chown -R flask:flask /src

USER flask

COPY app.py /src/app.py

WORKDIR /src

ENV FLASK_APP=app.py REDIS_HOST=redis

EXPOSE 5000

CMD ["flask", "run", "-h", "0.0.0.0"]
```

build image 並下載一個redis image

```shell
$ docker image pull redis
$ docker image build -t flask-demo .
$ docker image ls
REPOSITORY   TAG          IMAGE ID       CREATED              SIZE
flask-demo   latest       4778411a24c5   About a minute ago   126MB
python       3.9.5-slim   c71955050276   8 days ago           115MB
redis        latest       08502081bff6   2 weeks ago          105MB
```

建立一個bridge

```shell
$ docker network create -d bridge demo-network
8005f4348c44ffe3cdcbbda165beea2b0cb520179d3745b24e8f9e05a3e6456d
$ docker network ls
NETWORK ID     NAME           DRIVER    SCOPE
2a464c0b8ec7   bridge         bridge    local
8005f4348c44   demo-network   bridge    local
80b63f711a37   host           host      local
fae746a75be1   none           null      local

```

建立redis container 並連至 demo-network

```shell
$ docker container run -d --name redis-server --network demo-network redis
002800c265020310231d689e6fd35bc084a0fa015e8b0a3174aa2c5e29824c0e
$ docker container ls
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS      NAMES
002800c26502   redis     "docker-entrypoint.s…"   4 seconds ago   Up 3 seconds   6379/tcp   redis-server
```

建立 flask container

```shell
$ docker container run -d --network demo-network --name flask-demo --env REDIS_HOST=redis-server -p 5000:5000 flask-demo
```

打開瀏覽器 [http://127.0.0.1:5000](http://127.0.0.1:5000/)

可以看到以下內容，每次刷新次次就會+1  
Hello Container World! I have been seen <span style="color: #ff0000;">36</span> times and my hostname is 925ecb8d111a.

##### 整理後腳本

```shell
# prepare image
docker image pull redis
docker image build -t flask-demo .

# create network
docker network create -d bridge demo-network

# create container
docker container run -d --name redis-server --network demo-network redis
docker container run -d --network demo-network --name flask-demo --env REDIS_HOST=redis-server -p 5000:5000 flask-demo
```

# Docker Swarm

### 相關指令

```shell
# 查詢swarm狀態
docker info
# 未開啟
Swarm: inactive

# 初始化swarm
docker swarm init
##########################
Swarm initialized: current node (4szs3vf8x9pb79me4cxfsb9m2) is now a manager.

To add a worker to this swarm, run the following command:
	#加入叢集使用以下命令
    docker swarm join --token SWMTKN-1-3i2aq39bqqudp6p6y3o4ubw4ca1036z85z3huf9lyfijdfns88-33y43c8gw2ch85wbl2jdb4w0e 192.168.65.6:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
##########################

#查詢節點
docker node ls
ID                            HOSTNAME         STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
4szs3vf8x9pb79me4cxfsb9m2 *   docker-desktop   Ready     Active         Leader           20.10.6

# 開啟後查詢 docker info
Swarm: active
  NodeID: 4szs3vf8x9pb79me4cxfsb9m2
  Is Manager: true
  ClusterID: 83phccx66ca4rji17wk8p5kce
  Managers: 1
  Nodes: 1
  Default Address Pool: 10.0.0.0/8
  SubnetSize: 24
  Data Path Port: 4789
  Orchestration:
  
 # 離開docker swarm 叢集
 docker swrm leave -f
```

### docker swarm init 背後發生了什麼

主要是PKI和安全相關的自動化

- 創建swarm集群的根證書
- manager節點的證書
- 其它節點加入集群需要的tokens

創建Raft資料庫用於存儲證書，配置，密碼等數據

---

### 建立一個service(container)

```shell
# docker service create {image}:{tag}
docker service create --name web nginx:latest
```

```shell
# 取得servie id(不是container id)
docker service ls

ID             NAME           MODE         REPLICAS   IMAGE          PORTS
l5wvuxzw9l96   jolly_euclid   replicated   1/1        nginx:latest

# 查看container
docker service ps l5wvuxzw9l96
ID             NAME             IMAGE          NODE             DESIRED STATE   CURRENT STATE           ERROR     PORTS
vp8dkrsqslxw   jolly_euclid.1   nginx:latest   docker-desktop   Running         Running 3 minutes ago

# 建立3個副本(update)
docker service update l5wvuxzw9l96 --replicas 3
l5wvuxzw9l96
overall progress: 3 out of 3 tasks
1/3: running   [==================================================>]
2/3: running   [==================================================>]
3/3: running   [==================================================>]

# 建立4個副本(scale)
docker service scale l5wvuxzw9l96=4
web scaled to 4
overall progress: 4 out of 4 tasks 
1/4: running   [==================================================>] 
2/4: running   [==================================================>] 
3/4: running   [==================================================>] 
4/4: running   [==================================================>] 

#docker sercie ps
docker service ps l5wvuxzw9l96
ID             NAME             IMAGE          NODE             DESIRED STATE   CURRENT STATE            ERROR     PORTS
vp8dkrsqslxw   jolly_euclid.1   nginx:latest   docker-desktop   Running         Running 13 minutes ago
qavp0npxko3l   jolly_euclid.2   nginx:latest   docker-desktop   Running         Running 7 minutes ago
kqs6uvbkxmjh   jolly_euclid.3   nginx:latest   docker-desktop   Running         Running 7 minutes ago
```

### service 刪除

```shell
 docker service rm l5wvuxzw9l96
```

---

### docker swarm的網路：

- 第一是外部如何訪問部署運行在swarm集群內的服務，可以稱之為 `<span class="pre">入方向</span>` 流量，在swarm裡我們通過 `<span class="pre">ingress</span>` 來解決
- 第二是部署在swarm集群裡的服務，如何對外進行訪問，這部分又分為兩塊:
    
    
    - 第一，`<span class="pre">東西向流量</span>` ，也就是不同swarm節點上的容器之間如何通信，swarm通過 `<span class="pre">overlay</span>` 網絡來解決；
    - 第二，`<span class="pre">南北向流量</span>` ，也就是swarm集群裡的容器如何對外訪問，比如互聯網，這個是 `<span class="pre">Linux</span> <span class="pre">bridge</span> <span class="pre">+</span> <span class="pre">iptables</span> <span class="pre">NAT</span>` 來解決的

[![image-1644943091590.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-02/scaled-1680-/nxC5u1ibHLND7LXz-image-1644943091590.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-02/nxC5u1ibHLND7LXz-image-1644943091590.png)

# Docker如何固定IP設定的方法

[Docker如何固定IP設定的方法 - IT閱讀 (itread01.com)](https://www.itread01.com/article/1499743916.html)

經常用Docker模擬專案在生產環境中的部署，往往需要同時開好幾臺Docker容器，而且有時安裝的軟體需要繫結Docker區域網中的其他容器，如 MongoDB 副本集部署的時候，就需要繫結其他容器的內網IP。

但是，Docker 每次重啟後，容器的IP地址會變化，查詢了資料，Docker是支援設定固定IP的。

**Docker 預設網路**

Docker安裝後，預設會建立下面三種網路型別：

<div id="bkmrk-%24-docker-network-ls-">```
<span class="pln">$ docker network ls
NETWORK ID     NAME        DRIVER       SCOPE
</span><span class="lit">9781b1f585ae</span><span class="pln">    bridge       bridge       </span><span class="kwd">local</span>
<span class="lit">1252da701e55</span><span class="pln">    host        host        </span><span class="kwd">local</span>
<span class="lit">237ea3d5cfbf</span><span class="pln">    none        </span><span class="kwd">null</span>        <span class="kwd">local</span>
```

</div>啟動 Docker的時候，用 `--network `引數，可以指定網路型別，如：

<div id="bkmrk-%7E-docker-run--itd---">```
<span class="pun">~</span><span class="pln"> docker run </span><span class="pun">-</span><span class="pln">itd </span><span class="pun">--</span><span class="pln">name test1 </span><span class="pun">--</span><span class="pln">network bridge </span><span class="pun">--</span><span class="pln">ip </span><span class="lit">172.17</span><span class="pun">.</span><span class="lit">0.10</span><span class="pln"> centos</span><span class="pun">:</span><span class="pln">latest </span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">bash</span>
```

</div>**bridge：橋接網路**

預設情況下啟動的Docker容器，都是使用 bridge，Docker安裝時建立的橋接網路，每次Docker容器重啟時，會按照順序獲取對應的IP地址，這個就導致重啟下，Docker的IP地址就變了

**none：無指定網路**

使用 `--network=none `，docker 容器就不會分配區域網的IP

**host： 主機網路**

使用 `--network=host`，此時，Docker 容器的網路會附屬在主機上，兩者是互通的。

例如，在容器中執行一個Web服務，監聽8080埠，則主機的8080埠就會自動對映到容器中。

**建立自定義網路：（設定固定IP）**

啟動Docker容器的時候，使用預設的網路是不支援指派固定IP的，如下：

<div id="bkmrk-%7E-docker-run--itd----0">```
<span class="pun">~</span><span class="pln"> docker run </span><span class="pun">-</span><span class="pln">itd </span><span class="pun">--</span><span class="pln">net bridge </span><span class="pun">--</span><span class="pln">ip </span><span class="lit">172.17</span><span class="pun">.</span><span class="lit">0.10</span><span class="pln"> centos</span><span class="pun">:</span><span class="pln">latest </span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">bash
</span><span class="lit">6eb1f228cf308d1c60db30093c126acbfd0cb21d76cb448c678bab0f1a7c0df6</span><span class="pln">
docker</span><span class="pun">:</span> <span class="typ">Error</span><span class="pln"> response </span><span class="kwd">from</span><span class="pln"> daemon</span><span class="pun">:</span> <span class="typ">User</span><span class="pln"> specified IP address </span><span class="kwd">is</span><span class="pln"> supported on user </span><span class="kwd">defined</span><span class="pln"> networks only</span><span class="pun">.</span>
```

</div>因此，需要建立自定義網路，下面是具體的步驟：

**步驟1: 建立自定義網路**

建立自定義網路，並且指定網段：172.18.0.0/16

<div id="bkmrk-%E2%9E%9C-%7E-docker-network-c">```
<span class="pun">➜</span> <span class="pun">~</span><span class="pln"> docker network create </span><span class="pun">--</span><span class="pln">subnet</span><span class="pun">=</span><span class="lit">172.18</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">/</span><span class="lit">16</span><span class="pln"> mynetwork
</span><span class="pun">➜</span> <span class="pun">~</span><span class="pln"> docker network ls
NETWORK ID     NAME        DRIVER       SCOPE
</span><span class="lit">9781b1f585ae</span><span class="pln">    bridge       bridge       </span><span class="kwd">local</span>
<span class="lit">1252da701e55</span><span class="pln">    host        host        </span><span class="kwd">local</span>
<span class="lit">4f11ae9c85de</span><span class="pln">    mynetwork      bridge       </span><span class="kwd">local</span>
<span class="lit">237ea3d5cfbf</span><span class="pln">    none        </span><span class="kwd">null</span>        <span class="kwd">local</span>
```

</div>**步驟2: 建立Docker容器**

<u></u>程式碼如下:

➜ ~ docker run -itd --name networkTest1 --net mynetwork --ip 172.18.0.2 centos:latest /bin/bash

這個時候，建立的Docker容器就會持有 172.18.0.2 這個IP.

<div id="bkmrk-%5Broot%40ec8e31938fe7-%2F">```
<span class="pun">[</span><span class="pln">root@ec8e31938fe7</span> <span class="pun">/]#</span><span class="pln"> ifconfig
eth0   </span><span class="typ">Link</span><span class="pln"> encap</span><span class="pun">:</span><span class="typ">Ethernet</span> <span class="typ">HWaddr</span> <span class="lit">02</span><span class="pun">:</span><span class="lit">42</span><span class="pun">:</span><span class="pln">AC</span><span class="pun">:</span><span class="lit">12</span><span class="pun">:</span><span class="lit">00</span><span class="pun">:</span><span class="lit">02</span><span class="pln">
     inet addr</span><span class="pun">:</span><span class="lit">172.18</span><span class="pun">.</span><span class="lit">0.2</span> <span class="typ">Bcast</span><span class="pun">:</span><span class="lit">0.0</span><span class="pun">.</span><span class="lit">0.0</span> <span class="typ">Mask</span><span class="pun">:</span><span class="lit">255.255</span><span class="pun">.</span><span class="lit">0.0</span><span class="pln">
     inet6 addr</span><span class="pun">:</span><span class="pln"> fe80</span><span class="pun">::</span><span class="lit">42</span><span class="pun">:</span><span class="pln">acff</span><span class="pun">:</span><span class="pln">fe12</span><span class="pun">:</span><span class="lit">2</span><span class="pun">/</span><span class="lit">64</span> <span class="typ">Scope</span><span class="pun">:</span><span class="typ">Link</span><span class="pln">
     UP BROADCAST RUNNING MULTICAST MTU</span><span class="pun">:</span><span class="lit">1500</span> <span class="typ">Metric</span><span class="pun">:</span><span class="lit">1</span><span class="pln">
     RX packets</span><span class="pun">:</span><span class="lit">88</span><span class="pln"> errors</span><span class="pun">:</span><span class="lit">0</span><span class="pln"> dropped</span><span class="pun">:</span><span class="lit">0</span><span class="pln"> overruns</span><span class="pun">:</span><span class="lit">0</span><span class="pln"> frame</span><span class="pun">:</span><span class="lit">0</span><span class="pln">
     TX packets</span><span class="pun">:</span><span class="lit">14</span><span class="pln"> errors</span><span class="pun">:</span><span class="lit">0</span><span class="pln"> dropped</span><span class="pun">:</span><span class="lit">0</span><span class="pln"> overruns</span><span class="pun">:</span><span class="lit">0</span><span class="pln"> carrier</span><span class="pun">:</span><span class="lit">0</span><span class="pln">
     collisions</span><span class="pun">:</span><span class="lit">0</span><span class="pln"> txqueuelen</span><span class="pun">:</span><span class="lit">0</span><span class="pln">
     RX bytes</span><span class="pun">:</span><span class="lit">4056</span> <span class="pun">(</span><span class="lit">3.9</span> <span class="typ">KiB</span><span class="pun">)</span><span class="pln"> TX bytes</span><span class="pun">:</span><span class="lit">1068</span> <span class="pun">(</span><span class="lit">1.0</span> <span class="typ">KiB</span><span class="pun">)</span><span class="pln">

lo    </span><span class="typ">Link</span><span class="pln"> encap</span><span class="pun">:</span><span class="typ">Local</span> <span class="typ">Loopback</span><span class="pln">
     inet addr</span><span class="pun">:</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span> <span class="typ">Mask</span><span class="pun">:</span><span class="lit">255.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pln">
     inet6 addr</span><span class="pun">:</span> <span class="pun">::</span><span class="lit">1</span><span class="pun">/</span><span class="lit">128</span> <span class="typ">Scope</span><span class="pun">:</span><span class="typ">Host</span><span class="pln">
     UP LOOPBACK RUNNING MTU</span><span class="pun">:</span><span class="lit">65536</span> <span class="typ">Metric</span><span class="pun">:</span><span class="lit">1</span><span class="pln">
     RX packets</span><span class="pun">:</span><span class="lit">0</span><span class="pln"> errors</span><span class="pun">:</span><span class="lit">0</span><span class="pln"> dropped</span><span class="pun">:</span><span class="lit">0</span><span class="pln"> overruns</span><span class="pun">:</span><span class="lit">0</span><span class="pln"> frame</span><span class="pun">:</span><span class="lit">0</span><span class="pln">
     TX packets</span><span class="pun">:</span><span class="lit">0</span><span class="pln"> errors</span><span class="pun">:</span><span class="lit">0</span><span class="pln"> dropped</span><span class="pun">:</span><span class="lit">0</span><span class="pln"> overruns</span><span class="pun">:</span><span class="lit">0</span><span class="pln"> carrier</span><span class="pun">:</span><span class="lit">0</span><span class="pln">
     collisions</span><span class="pun">:</span><span class="lit">0</span><span class="pln"> txqueuelen</span><span class="pun">:</span><span class="lit">1</span><span class="pln">
     RX bytes</span><span class="pun">:</span><span class="lit">0</span> <span class="pun">(</span><span class="lit">0.0</span><span class="pln"> b</span><span class="pun">)</span><span class="pln"> TX bytes</span><span class="pun">:</span><span class="lit">0</span> <span class="pun">(</span><span class="lit">0.0</span><span class="pln"> b</span><span class="pun">)</span>
```

</div>

# Docker安裝

### Centos

[Docker Engine on CentOS | Docker Documentation](https://docs.docker.com/engine/install/centos/)

```highlight
 sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine
```

```
 sudo yum install -y yum-utils
 sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
```

```
 sudo yum-config-manager --enable docker-ce-nightly
 
 sudo yum install docker-ce docker-ce-cli containerd.io
 
 sudo systemctl start docker
 sudo systemctl enable docker
```

~/.docker/config.json

```
{
 "proxies":
 {
   "default":
   {
     "httpProxy": "http://192.168.1.12:3128",
     "httpsProxy": "http://192.168.1.12:3128",
     "noProxy": "*.test.example.com,.example2.com,127.0.0.0/8"
   }
 }
}
```

/etc/docker/daemon.json

```
{
"registry-mirrors":["http://hub-mirror.c.163.com"],
}

```

```
registry-1.docker.io.	54	IN	A	34.197.211.151
registry-1.docker.io.	54	IN	A	34.192.145.113
registry-1.docker.io.	54	IN	A	18.214.230.110
registry-1.docker.io.	54	IN	A	52.55.168.20
registry-1.docker.io.	54	IN	A	3.229.227.53
registry-1.docker.io.	54	IN	A	3.209.182.229
registry-1.docker.io.	54	IN	A	3.213.204.48
registry-1.docker.io.	54	IN	A	54.161.109.204
```

```
mkdir /etc/systemd/system/docker.service.d
vim /etc/systemd/system/docker.service.d/http-proxy.conf
########################################################
[Service]
Environment="HTTP_PROXY=http://proxy.example.com:80/"
Environment="NO_PROXY=localhost,127.0.0.0/8,docker-registry.somecorporation.com"

########################################################
sudo systemctl daemon-reload
sudo systemctl show --property Environment docker
sudo systemctl restart docker
```

---

### 使用get.docker.com 腳本

[https://get.docker.com](https://get.docker.com/)

```shell
 curl -L get.docker.com -o get-docker.sh
 sh get-docker.sh
```

```shell
#!/bin/sh
set -e
# Docker CE for Linux installation script
#
# See https://docs.docker.com/engine/install/ for the installation steps.
#
# This script is meant for quick & easy install via:
#   $ curl -fsSL https://get.docker.com -o get-docker.sh
#   $ sh get-docker.sh
#
# For test builds (ie. release candidates):
#   $ curl -fsSL https://test.docker.com -o test-docker.sh
#   $ sh test-docker.sh
#
# NOTE: Make sure to verify the contents of the script
#       you downloaded matches the contents of install.sh
#       located at https://github.com/docker/docker-install
#       before executing.
#
# Git commit from https://github.com/docker/docker-install when
# the script was uploaded (Should only be modified by upload job):
SCRIPT_COMMIT_SHA="93d2499759296ac1f9c510605fef85052a2c32be"

# strip "v" prefix if present
VERSION="${VERSION#v}"

# The channel to install from:
#   * nightly
#   * test
#   * stable
#   * edge (deprecated)
DEFAULT_CHANNEL_VALUE="stable"
if [ -z "$CHANNEL" ]; then
	CHANNEL=$DEFAULT_CHANNEL_VALUE
fi

DEFAULT_DOWNLOAD_URL="https://download.docker.com"
if [ -z "$DOWNLOAD_URL" ]; then
	DOWNLOAD_URL=$DEFAULT_DOWNLOAD_URL
fi

DEFAULT_REPO_FILE="docker-ce.repo"
if [ -z "$REPO_FILE" ]; then
	REPO_FILE="$DEFAULT_REPO_FILE"
fi

mirror=''
DRY_RUN=${DRY_RUN:-}
while [ $# -gt 0 ]; do
	case "$1" in
		--mirror)
			mirror="$2"
			shift
			;;
		--dry-run)
			DRY_RUN=1
			;;
		--*)
			echo "Illegal option $1"
			;;
	esac
	shift $(( $# > 0 ? 1 : 0 ))
done

case "$mirror" in
	Aliyun)
		DOWNLOAD_URL="https://mirrors.aliyun.com/docker-ce"
		;;
	AzureChinaCloud)
		DOWNLOAD_URL="https://mirror.azure.cn/docker-ce"
		;;
esac

command_exists() {
	command -v "$@" > /dev/null 2>&1
}

# version_gte checks if the version specified in $VERSION is at least
# the given CalVer (YY.MM) version. returns 0 (success) if $VERSION is either
# unset (=latest) or newer or equal than the specified version. Returns 1 (fail)
# otherwise.
#
# examples:
#
# VERSION=20.10
# version_gte 20.10 // 0 (success)
# version_gte 19.03 // 0 (success)
# version_gte 21.10 // 1 (fail)
version_gte() {
	if [ -z "$VERSION" ]; then
			return 0
	fi
	eval calver_compare "$VERSION" "$1"
}

# calver_compare compares two CalVer (YY.MM) version strings. returns 0 (success)
# if version A is newer or equal than version B, or 1 (fail) otherwise. Patch
# releases and pre-release (-alpha/-beta) are not taken into account
#
# examples:
#
# calver_compare 20.10 19.03 // 0 (success)
# calver_compare 20.10 20.10 // 0 (success)
# calver_compare 19.03 20.10 // 1 (fail)
calver_compare() (
	set +x

	yy_a="$(echo "$1" | cut -d'.' -f1)"
	yy_b="$(echo "$2" | cut -d'.' -f1)"
	if [ "$yy_a" -lt "$yy_b" ]; then
		return 1
	fi
	if [ "$yy_a" -gt "$yy_b" ]; then
		return 0
	fi
	mm_a="$(echo "$1" | cut -d'.' -f2)"
	mm_b="$(echo "$2" | cut -d'.' -f2)"
	if [ "${mm_a#0}" -lt "${mm_b#0}" ]; then
		return 1
	fi

	return 0
)

is_dry_run() {
	if [ -z "$DRY_RUN" ]; then
		return 1
	else
		return 0
	fi
}

is_wsl() {
	case "$(uname -r)" in
	*microsoft* ) true ;; # WSL 2
	*Microsoft* ) true ;; # WSL 1
	* ) false;;
	esac
}

is_darwin() {
	case "$(uname -s)" in
	*darwin* ) true ;;
	*Darwin* ) true ;;
	* ) false;;
	esac
}

deprecation_notice() {
	distro=$1
	distro_version=$2
	echo
	printf "\033[91;1mDEPRECATION WARNING\033[0m\n"
	printf "    This Linux distribution (\033[1m%s %s\033[0m) reached end-of-life and is no longer supported by this script.\n" "$distro" "$distro_version"
	echo   "    No updates or security fixes will be released for this distribution, and users are recommended"
	echo   "    to upgrade to a currently maintained version of $distro."
	echo
	printf   "Press \033[1mCtrl+C\033[0m now to abort this script, or wait for the installation to continue."
	echo
	sleep 10
}

get_distribution() {
	lsb_dist=""
	# Every system that we officially support has /etc/os-release
	if [ -r /etc/os-release ]; then
		lsb_dist="$(. /etc/os-release && echo "$ID")"
	fi
	# Returning an empty string here should be alright since the
	# case statements don't act unless you provide an actual value
	echo "$lsb_dist"
}

echo_docker_as_nonroot() {
	if is_dry_run; then
		return
	fi
	if command_exists docker && [ -e /var/run/docker.sock ]; then
		(
			set -x
			$sh_c 'docker version'
		) || true
	fi

	# intentionally mixed spaces and tabs here -- tabs are stripped by "<<-EOF", spaces are kept in the output
	echo
	echo "================================================================================"
	echo
	if version_gte "20.10"; then
		echo "To run Docker as a non-privileged user, consider setting up the"
		echo "Docker daemon in rootless mode for your user:"
		echo
		echo "    dockerd-rootless-setuptool.sh install"
		echo
		echo "Visit https://docs.docker.com/go/rootless/ to learn about rootless mode."
		echo
	fi
	echo
	echo "To run the Docker daemon as a fully privileged service, but granting non-root"
	echo "users access, refer to https://docs.docker.com/go/daemon-access/"
	echo
	echo "WARNING: Access to the remote API on a privileged Docker daemon is equivalent"
	echo "         to root access on the host. Refer to the 'Docker daemon attack surface'"
	echo "         documentation for details: https://docs.docker.com/go/attack-surface/"
	echo
	echo "================================================================================"
	echo
}

# Check if this is a forked Linux distro
check_forked() {

	# Check for lsb_release command existence, it usually exists in forked distros
	if command_exists lsb_release; then
		# Check if the `-u` option is supported
		set +e
		lsb_release -a -u > /dev/null 2>&1
		lsb_release_exit_code=$?
		set -e

		# Check if the command has exited successfully, it means we're in a forked distro
		if [ "$lsb_release_exit_code" = "0" ]; then
			# Print info about current distro
			cat <<-EOF
			You're using '$lsb_dist' version '$dist_version'.
			EOF

			# Get the upstream release info
			lsb_dist=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'id' | cut -d ':' -f 2 | tr -d '[:space:]')
			dist_version=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'codename' | cut -d ':' -f 2 | tr -d '[:space:]')

			# Print info about upstream distro
			cat <<-EOF
			Upstream release is '$lsb_dist' version '$dist_version'.
			EOF
		else
			if [ -r /etc/debian_version ] && [ "$lsb_dist" != "ubuntu" ] && [ "$lsb_dist" != "raspbian" ]; then
				if [ "$lsb_dist" = "osmc" ]; then
					# OSMC runs Raspbian
					lsb_dist=raspbian
				else
					# We're Debian and don't even know it!
					lsb_dist=debian
				fi
				dist_version="$(sed 's/\/.*//' /etc/debian_version | sed 's/\..*//')"
				case "$dist_version" in
					11)
						dist_version="bullseye"
					;;
					10)
						dist_version="buster"
					;;
					9)
						dist_version="stretch"
					;;
					8)
						dist_version="jessie"
					;;
				esac
			fi
		fi
	fi
}

do_install() {
	echo "# Executing docker install script, commit: $SCRIPT_COMMIT_SHA"

	if command_exists docker; then
		cat >&2 <<-'EOF'
			Warning: the "docker" command appears to already exist on this system.

			If you already have Docker installed, this script can cause trouble, which is
			why we're displaying this warning and provide the opportunity to cancel the
			installation.

			If you installed the current Docker package using this script and are using it
			again to update Docker, you can safely ignore this message.

			You may press Ctrl+C now to abort this script.
		EOF
		( set -x; sleep 20 )
	fi

	user="$(id -un 2>/dev/null || true)"

	sh_c='sh -c'
	if [ "$user" != 'root' ]; then
		if command_exists sudo; then
			sh_c='sudo -E sh -c'
		elif command_exists su; then
			sh_c='su -c'
		else
			cat >&2 <<-'EOF'
			Error: this installer needs the ability to run commands as root.
			We are unable to find either "sudo" or "su" available to make this happen.
			EOF
			exit 1
		fi
	fi

	if is_dry_run; then
		sh_c="echo"
	fi

	# perform some very rudimentary platform detection
	lsb_dist=$( get_distribution )
	lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')"

	if is_wsl; then
		echo
		echo "WSL DETECTED: We recommend using Docker Desktop for Windows."
		echo "Please get Docker Desktop from https://www.docker.com/products/docker-desktop"
		echo
		cat >&2 <<-'EOF'

			You may press Ctrl+C now to abort this script.
		EOF
		( set -x; sleep 20 )
	fi

	case "$lsb_dist" in

		ubuntu)
			if command_exists lsb_release; then
				dist_version="$(lsb_release --codename | cut -f2)"
			fi
			if [ -z "$dist_version" ] && [ -r /etc/lsb-release ]; then
				dist_version="$(. /etc/lsb-release && echo "$DISTRIB_CODENAME")"
			fi
		;;

		debian|raspbian)
			dist_version="$(sed 's/\/.*//' /etc/debian_version | sed 's/\..*//')"
			case "$dist_version" in
				11)
					dist_version="bullseye"
				;;
				10)
					dist_version="buster"
				;;
				9)
					dist_version="stretch"
				;;
				8)
					dist_version="jessie"
				;;
			esac
		;;

		centos|rhel|sles)
			if [ -z "$dist_version" ] && [ -r /etc/os-release ]; then
				dist_version="$(. /etc/os-release && echo "$VERSION_ID")"
			fi
		;;

		*)
			if command_exists lsb_release; then
				dist_version="$(lsb_release --release | cut -f2)"
			fi
			if [ -z "$dist_version" ] && [ -r /etc/os-release ]; then
				dist_version="$(. /etc/os-release && echo "$VERSION_ID")"
			fi
		;;

	esac

	# Check if this is a forked Linux distro
	check_forked

	# Print deprecation warnings for distro versions that recently reached EOL,
	# but may still be commonly used (especially LTS versions).
	case "$lsb_dist.$dist_version" in
		debian.stretch|debian.jessie)
			deprecation_notice "$lsb_dist" "$dist_version"
			;;
		raspbian.stretch|raspbian.jessie)
			deprecation_notice "$lsb_dist" "$dist_version"
			;;
		ubuntu.xenial|ubuntu.trusty)
			deprecation_notice "$lsb_dist" "$dist_version"
			;;
		fedora.*)
			if [ "$dist_version" -lt 33 ]; then
				deprecation_notice "$lsb_dist" "$dist_version"
			fi
			;;
	esac

	# Run setup for each distro accordingly
	case "$lsb_dist" in
		ubuntu|debian|raspbian)
			pre_reqs="apt-transport-https ca-certificates curl"
			if ! command -v gpg > /dev/null; then
				pre_reqs="$pre_reqs gnupg"
			fi
			apt_repo="deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] $DOWNLOAD_URL/linux/$lsb_dist $dist_version $CHANNEL"
			(
				if ! is_dry_run; then
					set -x
				fi
				$sh_c 'apt-get update -qq >/dev/null'
				$sh_c "DEBIAN_FRONTEND=noninteractive apt-get install -y -qq $pre_reqs >/dev/null"
				$sh_c "curl -fsSL \"$DOWNLOAD_URL/linux/$lsb_dist/gpg\" | gpg --dearmor --yes -o /usr/share/keyrings/docker-archive-keyring.gpg"
				$sh_c "echo \"$apt_repo\" > /etc/apt/sources.list.d/docker.list"
				$sh_c 'apt-get update -qq >/dev/null'
			)
			pkg_version=""
			if [ -n "$VERSION" ]; then
				if is_dry_run; then
					echo "# WARNING: VERSION pinning is not supported in DRY_RUN"
				else
					# Will work for incomplete versions IE (17.12), but may not actually grab the "latest" if in the test channel
					pkg_pattern="$(echo "$VERSION" | sed "s/-ce-/~ce~.*/g" | sed "s/-/.*/g").*-0~$lsb_dist"
					search_command="apt-cache madison 'docker-ce' | grep '$pkg_pattern' | head -1 | awk '{\$1=\$1};1' | cut -d' ' -f 3"
					pkg_version="$($sh_c "$search_command")"
					echo "INFO: Searching repository for VERSION '$VERSION'"
					echo "INFO: $search_command"
					if [ -z "$pkg_version" ]; then
						echo
						echo "ERROR: '$VERSION' not found amongst apt-cache madison results"
						echo
						exit 1
					fi
					if version_gte "18.09"; then
							search_command="apt-cache madison 'docker-ce-cli' | grep '$pkg_pattern' | head -1 | awk '{\$1=\$1};1' | cut -d' ' -f 3"
							echo "INFO: $search_command"
							cli_pkg_version="=$($sh_c "$search_command")"
					fi
					pkg_version="=$pkg_version"
				fi
			fi
			(
				pkgs=""
				if version_gte "18.09"; then
						# older versions don't support a cli package
						pkgs="$pkgs docker-ce-cli${cli_pkg_version%=}"
				fi
				if version_gte "20.10" && [ "$(uname -m)" = "x86_64" ]; then
						# also install the latest version of the "docker scan" cli-plugin (only supported on x86 currently)
						pkgs="$pkgs docker-scan-plugin"
				fi
				pkgs="$pkgs docker-ce${pkg_version%=}"
				if ! is_dry_run; then
					set -x
				fi
				$sh_c "DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends $pkgs >/dev/null"
				if version_gte "20.10"; then
					# Install docker-ce-rootless-extras without "--no-install-recommends", so as to install slirp4netns when available
					$sh_c "DEBIAN_FRONTEND=noninteractive apt-get install -y -qq docker-ce-rootless-extras${pkg_version%=} >/dev/null"
				fi
			)
			echo_docker_as_nonroot
			exit 0
			;;
		centos|fedora|rhel)
			if [ "$(uname -m)" != "s390x" ] && [ "$lsb_dist" = "rhel" ]; then
				echo "Packages for RHEL are currently only available for s390x."
				exit 1
			fi
			yum_repo="$DOWNLOAD_URL/linux/$lsb_dist/$REPO_FILE"
			if ! curl -Ifs "$yum_repo" > /dev/null; then
				echo "Error: Unable to curl repository file $yum_repo, is it valid?"
				exit 1
			fi
			if [ "$lsb_dist" = "fedora" ]; then
				pkg_manager="dnf"
				config_manager="dnf config-manager"
				enable_channel_flag="--set-enabled"
				disable_channel_flag="--set-disabled"
				pre_reqs="dnf-plugins-core"
				pkg_suffix="fc$dist_version"
			else
				pkg_manager="yum"
				config_manager="yum-config-manager"
				enable_channel_flag="--enable"
				disable_channel_flag="--disable"
				pre_reqs="yum-utils"
				pkg_suffix="el"
			fi
			(
				if ! is_dry_run; then
					set -x
				fi
				$sh_c "$pkg_manager install -y -q $pre_reqs"
				$sh_c "$config_manager --add-repo $yum_repo"

				if [ "$CHANNEL" != "stable" ]; then
					$sh_c "$config_manager $disable_channel_flag docker-ce-*"
					$sh_c "$config_manager $enable_channel_flag docker-ce-$CHANNEL"
				fi
				$sh_c "$pkg_manager makecache"
			)
			pkg_version=""
			if [ -n "$VERSION" ]; then
				if is_dry_run; then
					echo "# WARNING: VERSION pinning is not supported in DRY_RUN"
				else
					pkg_pattern="$(echo "$VERSION" | sed "s/-ce-/\\\\.ce.*/g" | sed "s/-/.*/g").*$pkg_suffix"
					search_command="$pkg_manager list --showduplicates 'docker-ce' | grep '$pkg_pattern' | tail -1 | awk '{print \$2}'"
					pkg_version="$($sh_c "$search_command")"
					echo "INFO: Searching repository for VERSION '$VERSION'"
					echo "INFO: $search_command"
					if [ -z "$pkg_version" ]; then
						echo
						echo "ERROR: '$VERSION' not found amongst $pkg_manager list results"
						echo
						exit 1
					fi
					if version_gte "18.09"; then
						# older versions don't support a cli package
						search_command="$pkg_manager list --showduplicates 'docker-ce-cli' | grep '$pkg_pattern' | tail -1 | awk '{print \$2}'"
						cli_pkg_version="$($sh_c "$search_command" | cut -d':' -f 2)"
					fi
					# Cut out the epoch and prefix with a '-'
					pkg_version="-$(echo "$pkg_version" | cut -d':' -f 2)"
				fi
			fi
			(
				if ! is_dry_run; then
					set -x
				fi
				# install the correct cli version first
				if [ -n "$cli_pkg_version" ]; then
					$sh_c "$pkg_manager install -y -q docker-ce-cli-$cli_pkg_version"
				fi
				$sh_c "$pkg_manager install -y -q docker-ce$pkg_version"
				if version_gte "20.10"; then
					$sh_c "$pkg_manager install -y -q docker-ce-rootless-extras$pkg_version"
				fi
			)
			echo_docker_as_nonroot
			exit 0
			;;
		sles)
			if [ "$(uname -m)" != "s390x" ]; then
				echo "Packages for SLES are currently only available for s390x"
				exit 1
			fi

			sles_version="${dist_version##*.}"
			sles_repo="$DOWNLOAD_URL/linux/$lsb_dist/$REPO_FILE"
			opensuse_repo="https://download.opensuse.org/repositories/security:SELinux/SLE_15_SP$sles_version/security:SELinux.repo"
			if ! curl -Ifs "$sles_repo" > /dev/null; then
				echo "Error: Unable to curl repository file $sles_repo, is it valid?"
				exit 1
			fi
			pre_reqs="ca-certificates curl libseccomp2 awk"
			(
				if ! is_dry_run; then
					set -x
				fi
				$sh_c "zypper install -y $pre_reqs"
				$sh_c "zypper addrepo $sles_repo"
				if ! is_dry_run; then
						cat >&2 <<-'EOF'
						WARNING!!
						openSUSE repository (https://download.opensuse.org/repositories/security:SELinux) will be enabled now.
						Do you wish to continue?
						You may press Ctrl+C now to abort this script.
						EOF
						( set -x; sleep 30 )
				fi
				$sh_c "zypper addrepo $opensuse_repo"
				$sh_c "zypper --gpg-auto-import-keys refresh"
				$sh_c "zypper lr -d"
			)
			pkg_version=""
			if [ -n "$VERSION" ]; then
				if is_dry_run; then
					echo "# WARNING: VERSION pinning is not supported in DRY_RUN"
				else
					pkg_pattern="$(echo "$VERSION" | sed "s/-ce-/\\\\.ce.*/g" | sed "s/-/.*/g")"
					search_command="zypper search -s --match-exact 'docker-ce' | grep '$pkg_pattern' | tail -1 | awk '{print \$6}'"
					pkg_version="$($sh_c "$search_command")"
					echo "INFO: Searching repository for VERSION '$VERSION'"
					echo "INFO: $search_command"
					if [ -z "$pkg_version" ]; then
						echo
						echo "ERROR: '$VERSION' not found amongst zypper list results"
						echo
						exit 1
					fi
					search_command="zypper search -s --match-exact 'docker-ce-cli' | grep '$pkg_pattern' | tail -1 | awk '{print \$6}'"
					# It's okay for cli_pkg_version to be blank, since older versions don't support a cli package
					cli_pkg_version="$($sh_c "$search_command")"
					pkg_version="-$pkg_version"

					search_command="zypper search -s --match-exact 'docker-ce-rootless-extras' | grep '$pkg_pattern' | tail -1 | awk '{print \$6}'"
					rootless_pkg_version="$($sh_c "$search_command")"
					rootless_pkg_version="-$rootless_pkg_version"
				fi
			fi
			(
				if ! is_dry_run; then
					set -x
				fi
				# install the correct cli version first
				if [ -n "$cli_pkg_version" ]; then
					$sh_c "zypper install -y  docker-ce-cli-$cli_pkg_version"
				fi
				$sh_c "zypper install -y docker-ce$pkg_version"
				if version_gte "20.10"; then
					$sh_c "zypper install -y docker-ce-rootless-extras$rootless_pkg_version"
				fi
			)
			echo_docker_as_nonroot
			exit 0
			;;
		*)
			if [ -z "$lsb_dist" ]; then
				if is_darwin; then
					echo
					echo "ERROR: Unsupported operating system 'macOS'"
					echo "Please get Docker Desktop from https://www.docker.com/products/docker-desktop"
					echo
					exit 1
				fi
			fi
			echo
			echo "ERROR: Unsupported distribution '$lsb_dist'"
			echo
			exit 1
			;;
	esac
	exit 1
}

# wrapped up in a function so that we have some protection against only getting
# half the file during "curl | sh"
do_install
```

# Docker架構

[![image-1644994790311.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-02/scaled-1680-/XEObQQ2Bh4FolaIj-image-1644994790311.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-02/XEObQQ2Bh4FolaIj-image-1644994790311.png)

[![image-1644994815207.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-02/scaled-1680-/5xLhezmrYbZpz2O4-image-1644994815207.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-02/5xLhezmrYbZpz2O4-image-1644994815207.png)

[![image-1644994935799.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-02/scaled-1680-/4KpqHL7bW0Xqz794-image-1644994935799.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-02/4KpqHL7bW0Xqz794-image-1644994935799.png)

# Docker常用指令

[![image-1641898072150.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/scaled-1680-/yQqsC2nE74AEf4ci-image-1641898072150.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-01/yQqsC2nE74AEf4ci-image-1641898072150.png)

\*container 運行

```shell
#run:
#-d 背景執行
#nginx 執行什麼服務
#ng 自訂的tagname
#-name 自訂名稱
# -e 設定環境變數, TZ=Asia/Taipei => 設定時區
docker container run -d nginx
docker container run -d -name ngx nginx
docker container run -d nginx:ng
docker container run -d -e "TZ=Asia/Taipei" nginx
```

\*查看運行container

```shell
docker container ls -a

[tomcat@docker-qc ~]$ docker container ls -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
df6ab3df4b14        nginx               "nginx -g 'daemon of…"   9 seconds ago       Up 8 seconds        80/tcp              modest_cray
940d3d804bcd        nginx               "nginx -g 'daemon of…"   7 minutes ago       Up 7 minutes        80/tcp              elastic_raman

```

\*運行container 並進入shell

```shell
docker container run -it {container name} sh
```

[![image-1637142725643.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-11/scaled-1680-/XbqLbfiUKtgdFbYz-image-1637142725643.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-11/XbqLbfiUKtgdFbYz-image-1637142725643.png)

\*進入已運行container

```shell
# 進入名為apache的容器執行sh
docker exec -it apache sh
```

\*複製檔案

```shell
# 建立一個hello.html
echo "hello" > hello.html
# 建立apache
docker run -d --name apache httpd:alpine
# 複製檔案至apache容器中
docker cp hello.html apache:/usr/local/apache2/htdocs/
# 複製容器中的檔案至本機
docker cp apache:/usr/local/apache2/htdocs/index.html ./index.html
```

\*掛載目錄

```shell
# 把hello.html掛成index.html
docker run -d --name apache2 -v $PWD/hello.html:/usr/local/apache2/htdocs/index.html httpd:alpine
```

\*建立外部連線

```shell
# -p port 對應 {本機}:{容器}
docker run -d -v $PWD.hell.html:/url/local/apache2/htdocs/index.html -p 80:80 httpd:alpine
```

---

\*刪除container

[![image-1637142432159.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-11/scaled-1680-/qOCJd5dGCtA5ZwlU-image-1637142432159.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-11/qOCJd5dGCtA5ZwlU-image-1637142432159.png)

```shell
#-f 強制
docker rm -f {container id}

#刪除所有容器
docker rm -f $(docker ps -a -q)
#刪除所有名字有demo的容器
docker rm -f $(docker ps -a  -q -f name=demo)

# 停用所有container
docker stop $(docker ps -a -q)
# 刪除沒已使用的container 與 image
docker system prune
# 刪除所有映像檔
docker rmi $(docker images -a -q)
# 刪除所有name = <none> 的映像檔
docker rmi $(docker images -f "dangling=true" -q)
# 刪除所有名稱叫shopcar的映像檔
docker rmi $(docker images shopcar* -q)

# 清除volume
docker volume rm $(docker volume ls)

# 刪除所有沒有使用的container image volume
docker system prune -a -f --volumes

#起一個不會停止的centos
docker run -d --name centos7 centos:7 sleep infinity
```

---

#### 查看要效能

\*從主機查看container process

[![image-1637142044315.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-11/scaled-1680-/VJEXMxCf0qyoGca5-image-1637142044315.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-11/VJEXMxCf0qyoGca5-image-1637142044315.png)

[![image-1637142037886.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-11/scaled-1680-/aeI7SiLajqZ2uD20-image-1637142037886.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-11/aeI7SiLajqZ2uD20-image-1637142037886.png)

```shell
docker container top {container id}
```

\*查看狀態

```shell
# 容器狀態
docker stats {container id | name }
# cpu ram
docker top {container id | name }
# log
docker logs {container id | name }
docker logs -f {container id | name }
```

---

# Dockerfile 的 /etc/hosts 如何設定

來源：

[Dockerfile 的 /etc/hosts 如何設定 – Tsung's Blog (longwin.com.tw)](https://blog.longwin.com.tw/2018/09/docker-dockerfile-etc-hosts-dns-setup-2018/)

## Dockerfile 的 /etc/hosts 如何設定

於 [Dockerfile reference](https://docs.docker.com/engine/reference/builder/) 文件裡面，沒有看到 /etc/hosts 該怎麼改，後來發現 /etc/hosts 修改的方式還蠻黑暗的~

修改 /etc/hosts 的作法有兩種，先講比較正確的方法：

Docker 增加 hosts 可以參考 Docker run 的文件：[Docker run reference](https://docs.docker.com/engine/reference/run/)

於 Docker run 使用 --add-host=server-name:127.0.0.1，多個用空白隔開，繼續寫下去即可，範例如下：

- **docker run ... --add-host='server:0.0.0.0' ...**
- **docker run ... --add-host='server:127.0.0.1' --add-host='server2:127.0.0.2'...**

若有 /etc/hosts 有太多需要增加，可以使用下述 Shell script 產生 --add-host 參數

- /etc/hosts 若要 1 vs 1 的 ip host 對應 
    - **cat /etc/hosts | sed 's/^#.\*//g' | grep '\[0-9\]\[0-9\]' | tr "\\t" " " | awk '{print "--add-host="$2":"$1 }' | tr '\\n' ' '**
- /etc/hosts 若要 1 vs n(多) 的 ip host 對應 
    - **cat /etc/hosts | grep -v "^#" | awk '{for(i=2;i&lt;=NF;i++){print "--add-host "$i":"$1}}'**

再來看比較暴力的作法：(直接寫在 Dockerfile 裡面)

```
# 使用 /tmp/hosts 當 /etc/hosts COPY /etc/hosts /tmp/hosts.bak
RUN cp hosts /tmp/hosts # hosts 於外部 cp /etc/hosts .
#RUN cp /etc/hosts /tmp/hosts #路径长度最好保持一致
RUN mkdir -p -- /lib-override && cp /lib/x86_64-linux-gnu/libnss_files.so.2 /lib-override
RUN sed -i 's:/etc/hosts:/tmp/hosts:g' /lib-override/libnss_files.so.2
ENV LD_LIBRARY_PATH /lib-override
RUN cat /tmp/hosts.bak >> /tmp/hosts
```

Docker 內部讀取 /etc/hosts 的部份，修改成讀取 **/tmp/hosts** (參考自此篇：[Docker 修改 hosts](https://wongxingjun.github.io/2016/04/06/Docker%E4%BF%AE%E6%94%B9hosts/))

將下述寫入 Dockerfile，於 build 的時候，就會讀取 /tmp/hosts 來操作囉~

### 相關網頁

- - [docker 之 dockerFile (解決 hosts，hostname 問題)](https://blog.csdn.net/libo222/article/details/52669093)
    - [docker - Modify hosts file in dockerfile](https://serverfault.com/questions/777714/modify-hosts-file-in-dockerfil)

# Docker 使用私有repo (Nexus)

```p1
vim /etc/docker/daemon.json
```

```
{
    "insecure-registries": ["10.10.10.10:8083"],
    "registry-mirrors": ["http://10.10.10.10:8083"]
}
```

```
sudo systemctl restart docker
```

[![image-1733823888931.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/Sun0E0r2v3qHI2mq-image-1733823888931.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/Sun0E0r2v3qHI2mq-image-1733823888931.png)

[![image-1733823909856.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/OeE7TnZi2zpJNQfu-image-1733823909856.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/OeE7TnZi2zpJNQfu-image-1733823909856.png)

https://registry-1.docker.io

[![image-1733824478204.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/k9GuHCfoM0j1FhJO-image-1733824478204.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/k9GuHCfoM0j1FhJO-image-1733824478204.png)

# Docker 內 container 如何使用Docker

在 Docker 容器內部直接執行宿主機上的 Docker 命令有一定的限制，因為 Docker 容器本身是與宿主機隔離的。以下是一些實現方法，根據不同需求選擇適合的解法：

---

### **方法 1：將 Docker socket 映射到容器**

將宿主機的 Docker socket (`/var/run/docker.sock`) 映射到容器內部，這樣容器內部的 Docker CLI 可以直接與宿主機的 Docker daemon 通訊。

#### **步驟**

1. **啟動容器時掛載 Docker socket：**
    
    ```bash
    docker run -it --rm \
      -v /var/run/docker.sock:/var/run/docker.sock \
      -v $(which docker):/usr/bin/docker \
      your-image
    
    ```
2. **在容器內使用 Docker 命令：** 容器內的 Docker CLI 可以直接操作宿主機的 Docker，執行例如 `docker ps`、`docker run` 等指令。
3. **注意安全性：** 授予容器訪問 Docker socket 的權限會帶來安全風險，因為容器內的進程可以完全控制宿主機的 Docker。僅在信任的環境下使用此方法。

---

### **方法 2：使用 Docker-in-Docker (DinD)**

如果希望在容器內運行獨立的 Docker daemon，可以使用 Docker 官方的 `docker:dind` 映像。

#### **步驟**

1. 啟動 DinD 容器：
    
    ```bash
    docker run --privileged --name dind-container -d docker:dind
    
    ```
2. 進入容器：
    
    ```bash
    docker exec -it dind-container sh
    
    ```
3. 在容器內執行 Docker 命令（這是獨立的 Docker daemon，不會影響宿主機的 Docker）。

---

### **方法 3：使用 Docker CLI 並通過 API 操作**

宿主機的 Docker daemon 提供 REST API，可以在容器內安裝 Docker CLI 並通過 API 操作。

#### **步驟**

1. 確保宿主機的 Docker daemon 開啟遠程 API，編輯 `/etc/docker/daemon.json`：
    
    ```json
    {
      "hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2375"]
    }
    
    ```
    
    然後重啟 Docker：
    
    ```bash
    sudo systemctl restart docker
    
    ```
2. 啟動容器，並配置環境變數 `DOCKER_HOST` 指向宿主機 API：
    
    ```bash
    docker run -it --rm \
      -e DOCKER_HOST=tcp://<host-ip>:2375 \
      your-image
    
    ```
3. 在容器內執行 Docker 命令：
    
    ```bash
    docker ps
    
    ```

---

### **注意事項**

1. **安全風險：**
    
    
    - 授予容器訪問 Docker socket 或 Docker daemon 的權限可能導致安全漏洞。
    - 確保只在受信任的環境中使用，並採取必要的隔離措施（例如：限制網路訪問、使用 TLS 保護 API）。
2. **性能影響：**
    
    
    - 如果容器內需要頻繁操作宿主機的 Docker，可能會影響宿主機的性能。
3. **權限問題：**
    
    
    - 確保容器內的進程有足夠的權限訪問 `/var/run/docker.sock` 或 Docker API。

希望這些方法能幫助你解決問題！如果需要更詳細的操作指引，可以告訴我具體的需求或環境設定。

# 【Docker】上傳自己的 Docker image 到 Docker Hub

要上傳自己的 Docker image 到 Docker Hub，請按照以下步驟操作：

### 1. 登入 Docker Hub

確保你的電腦已經安裝了 Docker，然後使用以下命令登入 Docker Hub：

```sh
docker login

```

系統會要求輸入你的 Docker Hub 帳號和密碼。

### 2. 建立 Docker Image

如果你還沒有 Docker image，可以使用 `docker build` 指令來建立：

```sh
docker build -t <你的DockerHub帳號>/<image名稱>:<tag> .

```

例如：

```sh
docker build -t myusername/myapp:v1 .

```

這樣就會建立一個 `myusername/myapp` 的 image，標籤（tag）為 `v1`。

### 3. 標記 (Tag) Image

如果你已經有一個本地的 image，但還沒有加上 Docker Hub 的 repository 名稱，你需要先標記它：

```sh
docker tag <本地image ID或名稱> <你的DockerHub帳號>/<image名稱>:<tag>

```

例如：

```sh
docker tag myapp:latest myusername/myapp:v1

```

### 4. 推送 (Push) Image

將 image 推送到 Docker Hub：

```sh
docker push <你的DockerHub帳號>/<image名稱>:<tag>

```

例如：

```sh
docker push myusername/myapp:v1

```

### 5. 確認上傳成功

你可以到 [Docker Hub](https://hub.docker.com/) 查看你的 repository，或者在其他機器上使用以下命令來拉取（pull）該 image：

```sh
docker pull myusername/myapp:v1

```

### 常見問題

1. **登入失敗**：
    
    
    - 如果 `docker login` 失敗，可以先在 Docker Hub 上確認你的帳號密碼是否正確。
    - 如果你使用的是 Docker Hub 產生的 Access Token，請在密碼欄位輸入該 Token。
2. **推送失敗，沒有權限 (denied: requested access to the resource is denied)**：
    
    
    - 確保你的 image tag 是 `你的DockerHub帳號/你的image名稱`，而不是本地的名稱。
    - 使用 `docker login` 確保你已登入正確的帳號。

這樣你就能成功上傳自己的 Docker image 到 Docker Hub 了！ 🚀

# 【Docker】自動分配網路用盡

failed to create network pub-sub\_pubsub: Error response from daemon: could not find an available, non-overlapping IPv4 address pool among the defaults to assign to the network

👉 **這個錯誤的意思是**：

> Docker **沒有可用的 IP 網段** 來建立新的 network。

Docker 每次起新的 network，會自動從預設的 IP range (例如 172.18.0.0/16) 分配一段子網（subnet）來給你的 `docker-compose`。  
但因為你本機已經有太多 Docker network（或之前的 container network 沒清乾淨），導致 **IP address range 已經用光了**。

解法，詳細往下看

```YAML
# 查詢目前所有container網段
docker network inspect $(docker network ls -q) --format '{{.Name}}: {{(index .IPAM.Config 0).Subnet}}'


# 刪除無用network 
docker network prune

#解法，指定網段  
networks:
  ngtest:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/24

```

---

# 🛠️ 解法 1：刪掉無用的 Docker Network

先看一下目前的 network：

```bash
docker network ls

```

會看到一堆像這樣：

<table id="bkmrk-network-id-name-driv"><thead><tr><th>NETWORK ID</th><th>NAME</th><th>DRIVER</th><th>SCOPE</th></tr></thead><tbody><tr><td>1a2b3c4d5e</td><td>bridge</td><td>bridge</td><td>local</td></tr><tr><td>6f7g8h9i0j</td><td>pub-sub\_pubsub</td><td>bridge</td><td>local</td></tr><tr><td>...</td><td>...</td><td>...</td><td>...</td></tr></tbody></table>

然後手動刪掉沒用的 network，例如：

```bash
docker network rm network_name

```

例如：

```bash
docker network rm pub-sub_pubsub

```

要一口氣刪很多：

```bash
docker network prune

```

（⚡小心！`prune` 會**把所有未使用的網路刪掉**。）

---

# 🛠️ 解法 2：docker-compose.yml 手動指定 IP 範圍

如果你想避免這個問題，以後可以**自己在 `docker-compose.yml` 裡面設定固定的子網段**，例如：

```yaml
networks:
  pubsub-net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.30.0.0/16

```

然後每個 service 指定 network：

```yaml
services:
  pubsub-emulator:
    image: google/cloud-sdk:latest
    networks:
      - pubsub-net

```

這樣 Docker 就不會自己亂分配 IP，會用你指定的 `172.30.0.0/16`，可以大大降低出錯的機會。

---

# 🔥 最快速推薦操作

直接下這兩行，清除掉無用網路 ➔ 再重開 docker-compose：

```bash
docker network prune -f
docker-compose up

```

---

# 🛎️ 小提醒

<table id="bkmrk-%E5%95%8F%E9%A1%8C%E6%83%85%E5%A2%83-%E8%AA%AA%E6%98%8E-%E9%80%9A%E5%B8%B8%E5%87%BA%E7%8F%BE%E5%9C%A8-docker"><thead><tr><th align="left">問題情境</th><th align="left">說明</th></tr></thead><tbody><tr><td align="left">通常出現在 Docker Desktop 很久沒清</td><td align="left">每次起 network，Docker 自動分配，結果越堆越多</td></tr><tr><td align="left">`docker network ls` 看太多 bridge</td><td align="left">可以確認哪些是 zombie network</td></tr><tr><td align="left">`docker-compose down` 不一定會清 network</td><td align="left">要自己下 `prune` 或 `network rm`</td></tr></tbody></table>

---

好問題！  
你問的這個其實就是：

> **如何查目前 Docker 所有 network 的網段（subnet），確保自己新建的網段不會衝突？**

這個是專業級的 docker networking 操作了，我來直接教你最正確、完整又快速的方法 👨🏻‍💻

---

# ✅ 查詢目前所有 Docker network 網段的方法

直接打：

```bash
docker network inspect $(docker network ls -q) --format '{{.Name}}: {{(index .IPAM.Config 0).Subnet}}'

```

這條指令的意思是：

- `docker network ls -q`：列出所有 network 的 ID
- `docker network inspect`：逐個去查 network 詳細資訊
- `--format`：只輸出你要看的部分（名字 + 子網範圍）

👉 出來結果像這樣：

```
bridge: 172.17.0.0/16
host: 
none: 
myproject_default: 172.21.0.0/16
another-network: 172.22.0.0/16

```

如果某些 network 是空的（像 `host`、`none`），那是正常的，因為它們沒有自己管理 IP。

---

# 🛠️ 查完之後怎麼重新分配？

- 看到目前用掉哪些子網了（例如 172.17.0.0/16、172.21.0.0/16）
- 選一個**完全沒重疊**的新網段來建你的 network，例如：
    
    
    - `172.28.0.0/24`
    - `172.30.0.0/24`
    - `192.168.100.0/24`
    - `10.20.0.0/24`
- 然後在你的 `docker-compose.yml` 裡設定你自己的網段！

（一般建議用 172.x.x.x / 192.168.x.x / 10.x.x.x 這些 private 網段）

---

# 🔥 進階版（更詳細看）

如果你想要看得更細，例如看到每個 container 的 IP，可以打：

```bash
docker network inspect network-name

```

例：

```bash
docker network inspect bridge
docker network inspect pubsub-net

```

它會印出一個超大 JSON，裡面會列出：

- 網段 subnet
- gateway
- 已連接的 container 名稱和 IP 地址

---

# 🛎️ 小結論

<table id="bkmrk-%E4%BD%A0%E6%83%B3%E5%81%9A%E4%BB%80%E9%BA%BC-%E6%8C%87%E4%BB%A4-%E7%9C%8B%E6%89%80%E6%9C%89-network"><thead><tr><th align="left">你想做什麼</th><th align="left">指令</th></tr></thead><tbody><tr><td align="left">看所有 network 網段</td><td align="left">`docker network inspect $(docker network ls -q) --format '{{.Name}}: {{(index .IPAM.Config 0).Subnet}}'`</td></tr><tr><td align="left">詳細看某個 network</td><td align="left">`docker network inspect network-name`</td></tr></tbody></table>

---

# 【Docker】image tage 比較

當然可以！我們來一一比較你提到的這些 Docker image tag（以 Node.js 為例常見的 tag 形式），重點在於：

> ❗**它們的差異主要在底層作業系統的發行版與體積，會影響相容性、功能完整度、啟動速度與安全更新週期。**

---

## ✅ 比較總覽表

<table id="bkmrk-tag-%E5%BA%95%E5%B1%A4%E7%B3%BB%E7%B5%B1-%E9%AB%94%E7%A9%8D%EF%BC%88%E6%A6%82%E4%BC%B0%EF%BC%89-%E7%9B%B8%E5%AE%B9%E6%80%A7-"><thead><tr><th>Tag</th><th>底層系統</th><th>體積（概估）</th><th>相容性</th><th>套件完整度</th><th>適合場景</th></tr></thead><tbody><tr><td>`20.19.3`</td><td>Debian（預設）</td><td>🟠 中（~100MB）</td><td>✅ 高</td><td>✅ 完整</td><td>開發 / 安全穩定部署</td></tr><tr><td>`20.19.3-slim`</td><td>Debian（精簡）</td><td>🟡 較小（~30–50MB）</td><td>✅ 高</td><td>🟡 精簡套件</td><td>精簡部署，但仍需套件支援</td></tr><tr><td>`20.19.3-bullseye`</td><td>Debian 11 (Bullseye)</td><td>🟠 中</td><td>✅ 高</td><td>✅ 完整</td><td>LTS 開發與部署</td></tr><tr><td>`20.19.3-bullseye-slim`</td><td>Debian 11 (精簡)</td><td>🟡 較小</td><td>✅ 高</td><td>🟡 精簡</td><td>比 `slim` 更指定版本的精簡版</td></tr><tr><td>`20.19.3-bookworm`</td><td>Debian 12 (Bookworm)</td><td>🟠 中</td><td>✅ 高</td><td>✅ 完整</td><td>新版 Debian，未來趨勢</td></tr><tr><td>`20.19.3-bookworm-slim`</td><td>Debian 12 (精簡)</td><td>🟡 較小</td><td>✅ 高</td><td>🟡 精簡</td><td>更「新」的精簡版本</td></tr><tr><td>`20.19.3-alpine3.22`</td><td>Alpine Linux 3.22</td><td>🟢 超小（~5MB）</td><td>⚠️ 中</td><td>❌ 非常精簡</td><td>超小部署映像，需自行加套件</td></tr><tr><td>`20.19.3-alpine`</td><td>最新 Alpine 版本</td><td>🟢 超小</td><td>⚠️ 中</td><td>❌ 非常精簡</td><td>最新 alpine，可能有相容性風險</td></tr></tbody></table>

---

## 🧩 詳細說明與比較依據

### 1️⃣ **無尾碼版本：`20.19.3`**

- 預設會用 **Debian（目前是 Bookworm 或 Bullseye）**
- 最穩定、最多人支援的標準版本
- 套件支援完整，但 image 比 slim 大

---

### 2️⃣ **slim 系列（`slim`, `bullseye-slim`, `bookworm-slim`）**

- 相比完整版，移除了 **man pages、locale 資料、編譯工具**
- 仍然保留 Debian 的核心相容性
- 較小但仍易於除錯與擴充，比 alpine 安全穩

---

### 3️⃣ **alpine 系列（`alpine`, `alpine3.22`）**

- 超小型（~5MB），非常適合部署
- 缺點：需額外安裝 libc（有時需 glibc，不然會出現 `segmentation fault`）
- 有些 Node 原生模組會無法正常編譯
- 建議只在熟悉 Alpine Linux 的情況下使用

---

### 4️⃣ **bullseye vs. bookworm**

- `bullseye` = Debian 11（2021 發布）
- `bookworm` = Debian 12（2023 發布，目前最新版）

> 若你要做長期部署，**bookworm 更新、更長期支援**；但部分舊系統（如 AWS AMI、某些 lib）可能還用 bullseye。

---

## ✅ 實務建議

<table id="bkmrk-%E9%9C%80%E6%B1%82-%E5%BB%BA%E8%AD%B0%E4%BD%BF%E7%94%A8-%E8%A6%81%E6%9C%80%E7%A9%A9%E3%80%81%E9%96%8B%E7%99%BC%E8%88%87%E9%83%A8%E7%BD%B2%E5%85%BC%E9%A1%A7-"><thead><tr><th>需求</th><th>建議使用</th></tr></thead><tbody><tr><td>要最穩、開發與部署兼顧</td><td>`20.19.3` 或 `20.19.3-bookworm`</td></tr><tr><td>部署在 Docker，但想減少容量</td><td>`20.19.3-slim` 或 `20.19.3-bookworm-slim`</td></tr><tr><td>專案熟悉 Alpine、對大小敏感</td><td>`20.19.3-alpine`（但需額外測試）</td></tr><tr><td>你想明確指定環境版本以便重建一致性</td><td>`20.19.3-bullseye` / `bookworm` / `alpine3.22` 等有版本號的更佳</td></tr></tbody></table>

---

## 📌 附加提醒

- 有些 Node 原生模組（如 `bcrypt`, `sharp`, `canvas`）在 `alpine` 上常常會遇到編譯問題。
- `slim` 不代表最小，只是比完整版小；真正小的是 `alpine`。
- 不同 tag 最安全的選法是「**明確指定版本與 base image**」，可確保可重現性。

---

# ELK

# ElasticSearch 啟動

### Starting Elasticsearch

[Starting Elasticsearch | Elasticsearch Guide \[7.14\] | Elastic](https://www.elastic.co/guide/en/elasticsearch/reference/current/starting-elasticsearch.html)

# Elasticsearch REST Api

[喬叔的 Elasticsearch 基礎實務班 (2021.07.24~25) - HackMD](https://hackmd.io/YZNLmE4ZRs28v5XX5lplDg)

[![image-1631848723417.png](http://bookstack.treemanou.com/uploads/images/gallery/2021-09/scaled-1680-/CxAtmrHBPXONsorz-image-1631848723417.png)](http://bookstack.treemanou.com/uploads/images/gallery/2021-09/CxAtmrHBPXONsorz-image-1631848723417.png)

[![image-1631848837295.png](http://bookstack.treemanou.com/uploads/images/gallery/2021-09/scaled-1680-/Heg90G0f3b0Pff09-image-1631848837295.png)](http://bookstack.treemanou.com/uploads/images/gallery/2021-09/Heg90G0f3b0Pff09-image-1631848837295.png)

Index與type欄位是必要的,id若省去,Elasticsearch  
會為document加上自動產生的id。  
– 自動產生的id為22字元長、URL-safe、Base64-encoded  
string UUID。

- 若給予的index或type名稱目前並不存在時  
    ,Elasticsearch會以預設設定值自動建立該名稱的  
    index或type。
- 若不給予id時,index資料應使用POST。
- type 建議使用 \_doc (since 6.0)

# 【Elasticsearch】容量100%

```shell
# 替換 <your_elasticsearch_host> 和 <index_name_pattern> 為實際的 Elasticsearch 主機和索引名稱模式

# 列出所有索引
curl -X GET "http://<your_elasticsearch_host>:9200/_cat/indices?v"

# 删除特定模式的索引（例如，以 "log-" 开头的索引）
curl -X DELETE "http://<your_elasticsearch_host>:9200/<index_name_pattern>"

# 删除所有索引（請謹慎使用）
# curl -X DELETE "http://<your_elasticsearch_host>:9200/*"
```

# Elasticsearch search

關於搜尋

[Elastic Stack第七重 - iT 邦幫忙::一起幫忙解決難題，拯救 IT 人的一天 (ithome.com.tw)](https://ithelp.ithome.com.tw/articles/10236812)

# 【Filebeat】Mac 安裝

 mac 安裝

https://www.elastic.co/guide/en/beats/filebeat/8.7/filebeat-installation-configuration.html#installation

```
 brew install filebeat

```

```
zsh completions have been installed to:
  /usr/local/share/zsh/site-functions

To restart filebeat after an upgrade:
  brew services restart filebeat
Or, if you don't want/need a background service you can just run:
  /usr/local/opt/filebeat/bin/filebeat

```

# 【Filebeat】相關連結

- [【轉】使用 Filebeat 應該要了解的設計細節與原理](https://training.onedoggo.com/tech-sharing/uncle-joe-teach-es-elastc-observability/logs-wa-jue-xi-tong-nei-bu-fa-sheng-de-zhuang-kuang/shi-yong-filebeat-ying-gai-yao-le-jie-de-she-ji-xi-jie-yu-yuan-li)
- [<span data-slate-fragment="JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMmlubGluZSUyMiUyQyUyMnR5cGUlMjIlM0ElMjJsaW5rJTIyJTJDJTIyaXNWb2lkJTIyJTNBZmFsc2UlMkMlMjJkYXRhJTIyJTNBJTdCJTIycmVmJTIyJTNBJTdCJTIya2luZCUyMiUzQSUyMnVybCUyMiUyQyUyMnVybCUyMiUzQSUyMmh0dHBzJTNBJTJGJTJGd3d3LmVsYXN0aWMuY28lMkZndWlkZSUyRmVuJTJGYmVhdHMlMkZmaWxlYmVhdCUyRjcuMTUlMkZjb25maWd1cmF0aW9uLWZpbGViZWF0LW9wdGlvbnMuaHRtbCUyMiU3RCU3RCUyQyUyMm5vZGVzJTIyJTNBJTVCJTdCJTIyb2JqZWN0JTIyJTNBJTIydGV4dCUyMiUyQyUyMmxlYXZlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMmxlYWYlMjIlMkMlMjJ0ZXh0JTIyJTNBJTIyJUU1JUFFJTk4JUU2JTk2JUI5JUU2JTk2JTg3JUU0JUJCJUI2JTIwLSUyMEZpbGViZWF0JTIwSW5wdXRzJTIyJTJDJTIybWFya3MlMjIlM0ElNUIlNUQlMkMlMjJzZWxlY3Rpb25zJTIyJTNBJTVCJTVEJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyN2U2NzMxOTBmN2JhNDhiYmIxMmI1MmQ4OWM1OWRiYjQlMjIlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjJmZjJhZmJmMDBmNWI0YzdkOGE4YzgzYTMzZmY5ZjY0NyUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjY1ZjNjNzhjMzEzNjQ5NWY5YjQyMTJmOGE3NGVhMWE2JTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyYzYyY2Q3ZWNhYTI2NGM5OTljN2U2YmQ0ZjNjYjg4MzElMjIlN0Q=">官方文件 - Filebeat Inputs</span>](https://www.elastic.co/guide/en/beats/filebeat/7.15/configuration-filebeat-options.html)
- [喬叔帶你上手 Elastic Stack - 探索與實踐 Observability 系列](https://training.onedoggo.com/tech-sharing/uncle-joe-teach-es-elastc-observability)

# Elasticsearch 查詢語法

query

```
查询一级标签
query
    二级标签
    prefix  wildcard
    constant_score
        filter
    prefix  前缀匹配,文档中的词语以搜索的词汇开头
        {field:value}
	term
	    代表完全匹配,即不进行分词器分析,文档中必须包含整个搜索的词汇
	    {field:value}
	match
	    match和term的区别是,match查询的时候,elasticsearch会根据你给定的字段提供合适的分析器,而term查询不会有分析器分析的过程
        match查询相当于模糊匹配,只包含其中一部分关键词就行
	    {field:value}
	match_all   没有参数,查询出来所有
	    {}
	match_phrase    匹配一个短语,给一个短语词,匹配
        {field:value}
    multi_match     匹配多个字段
        {
          "query": {
            "multi_match": {
              "query": "caoke2",
              "fields": [
                "msg",
                "code"
              ]
            }
          }
        }
	range
	    {"age":{"lt":10,"gt":1}}
	-- 以上两级关键字 不能同时使用 多关键字需要用bool
	bool    布尔查询,匹配多个条件,下面的关键字中可以把上面的再嵌套一遍 bool中不能包含range,要用filter包裹
		must should must_not filter
        多个查询条件用数组包裹
highlight   自定义标签的前后缀,fields 里面的字段要是query中查询的字段
    "highlight":{
        "pre_tags":[
          "<aaa>"
        ],
        "post_tags":[
          "</aaa>"
        ],
        "require_field_match":true,
        "fields":{
          "msg":{
            "fragment_size":1000,
            "number_of_fragments":0,
            "fragment_offset":0
          }
        }
      }
from    查询的结果取范围开始
size    查询的结果取范围结束
sort    排序
      "sort": [
        {
          "age": "desc"
        }
      ]
_source     包含or排除字段
    {"includes":["code"],"excludes":[]}
script_fields
aggs    聚合
    "aggregations":{
        "term_agg":{
          "terms":{
            "field":"age",  对字段分组
            "size":10,      返回的条数
            "order":[{   对返回的结果排序，可以多条
                "min_agg":"desc"
            }]
          },
          "aggregations":{  分组后需要统计，可以多个
            "min_agg":{
              "min":{
                "field":"age"
              }
            }
          }
        }
    }
```

# 【Kibana】 使用 https

要在本機環境的 Kibana 自產憑證，並讓 Kibana 支援 **HTTPS**，而非預設的 **HTTP**，以下是詳細步驟：

---

## 🚀 **步驟 1：產生自簽名憑證（Self-Signed Certificate）**

### 🔧 **使用 OpenSSL 產生憑證：**

1. **開啟終端機**，執行以下指令來建立一個新的私鑰和自簽名憑證：

```bash
mkdir -p /etc/kibana/ssl
cd /etc/kibana/ssl
# 產生私鑰和自簽名憑證，設定有效期限為 365 天
openssl req -x509 -newkey rsa:4096 -keyout kibana-key.pem -out kibana-cert.pem -days 365 -nodes
# 修改擁有者，權限
chown kibana:kibana kibana-key.pem
chown kibana:kibana kibana-cert.pem
chmod 644 kibana-key.pem
chmod 600 kibana-key.pem
```

### 💡 **輸入相關資訊：**

在執行上述指令時，OpenSSL 會要求輸入一些資訊，像是：

- **Country Name (國家代碼)**：TW
- **State or Province Name (省/市)**：Taipei
- **Locality Name (城市)**：Taipei
- **Organization Name (公司名稱)**：momo.com
- **Common Name (CN)**：apielkmonitor.momoshop.com.tw

Country Name (2 letter code) \[AU\]:<span style="color: #e03e2d;">TW</span>  
State or Province Name (full name) \[Some-State\]:<span style="color: #e03e2d;">Taipei</span>  
Locality Name (eg, city) \[\]:<span style="color: #e03e2d;">Taipei</span>  
Organization Name (eg, company) \[Internet Widgits Pty Ltd\]:<span style="color: #e03e2d;">momo.com</span>  
Organizational Unit Name (eg, section) \[\]:  
Common Name (e.g. server FQDN or YOUR name) \[\]:<span style="color: #e03e2d;">apielkmonitor.momoshop.com.tw</span>  
Email Address \[\]:srou@fmt.com.tw

### 🎯 **產生的檔案：**

- kibana-cert.pe：私鑰
- kibana-cert.pe：自簽名憑證

---

## 🚀 **步驟 2：配置 Kibana 使用 HTTPS**

### 🔧 **編輯 Kibana 的設定檔 `kibana.yml`：**

打開 **`kibana.yml`** 檔案（通常位於 `/etc/kibana/kibana.yml` 或你的安裝目錄下），修改以下內容：

```yaml
# 啟用 SSL
server.ssl.enabled: true

# 設定 SSL 憑證和私鑰的路徑
server.ssl.certificate: /etc/kibana/ssl/kibana-crt.pem
server.ssl.key: /etc/kibana/ssl/kibana-key.pem
# 修改 Kibana 的監聽端口
server.port: 443


# 如果elasticsearch 使用ssl 連線(沒有則不用)
# 選擇驗證證書的方式
elasticsearch.ssl.verificationMode: full
# 這個證書在 Elasticsearch 的 HTTP 證書時已經生成。複製到 Kibana 安裝目錄下配置使用即可。
elasticsearch.ssl.certificateAuthorities: [ "/data/kibana-8.13.0/elasticsearch-ca.pem" ]

```

> ⚠️ **注意**：將 `/path/to/` 替換成你實際的憑證路徑。

---

## 🚀 **步驟 3：允許本機信任自簽名憑證**

### 🖥️ **Mac 或 Linux：**

將自簽名憑證 **kibana.crt** 匯入你的系統信任區：

```bash
sudo cp kibana.crt /usr/local/share/ca-certificates/kibana.crt
sudo update-ca-certificates

```

### 🖥️ **Windows：**

1. 打開 **證書管理**。
2. 選擇 **受信任的根憑證授權單位**。
3. 匯入 **kibana.crt** 檔案。

---

## 🚀 **步驟 4：重啟 Kibana**

```bash
sudo systemctl restart kibana

```

---

## 🎯 **步驟 5：透過 HTTPS 訪問 Kibana**

現在，你可以使用 **`https://localhost`**（或指定的 IP 地址）來訪問 Kibana。

---

### 錯誤處理:

#### **FATAL Error: listen EACCES: permission denied 0.0.0.0:443**

1. ```shell
    # 啟動服務錯誤
    sudo systemctl start kibana 
    # 可以使用以下語法查看詳細錯誤
    journalctl -u kibana -f
    
    #如果出現以下錯誤，代表不允許非root人員啟用1024 port 以下服務
    FATAL  Error: listen EACCES: permission denied 0.0.0.0:443
    
    #使用以下語法同意以下檔案綁定使用1024 port 以下服務
    setcap cap_net_bind_service=+epi /usr/share/kibana/bin/kibana
    setcap cap_net_bind_service=+epi /usr/share/kibana/bin/kibana-plugin
    setcap cap_net_bind_service=+epi /usr/share/kibana/bin/kibana-keystore
    # node以實際位置為主
    setcap cap_net_bind_service=+epi /usr/share/kibana/node/glibc-217/bin/node
    ```

#### **Error: EACCES: permission denied, open '/run/kibana/kibana.pid'**

```
chown kibana:kibana /run/kibana/kibana.pid 
setcap cap_net_bind_service=+epi /run/kibana/kibana.pid
```

# 【 錯誤處理】watermark 設定太小導致index 變成唯讀

##### Log 出現:

##### <span class="s1">filebeat-7.1</span><span class="s1">7.7-app-integrity-ap-prod-2025.11.09\] blocked by: \[TOO\_MANY\_REQUESTS/12/disk usage exceeded flood-st</span><span class="s1">age watermark, index has read-only-allow-delete block\];</span>

---

以下是 **Elasticsearch 出現 flood-stage watermark**（磁碟使用率過高）導致 index 變成 **read-only-allow-delete** 的完整處理方式（你目前的錯誤訊息就是這個觸發的）。

---

# ✅ 錯誤原因

你的 Filebeat / Logstash 嘗試寫入索引  
`filebeat-7.7-app-integrity-ap-prod-2025.11.09`

但 Elasticsearch 回覆：

```
blocked by: [TOO_MANY_REQUESTS/12/disk usage exceeded flood-stage watermark, index has read-only-allow-delete block]

```

這代表：

1. **磁碟使用量超過 ES flood-stage watermark（預設 95%）**
2. ES 自己把 index 設成 **唯讀（read-only-allow-delete）**
3. 所有寫入都會 429 / 403

ES 為了保護資料，所以禁止寫入。

---

# ✅ 解決方式

## Step 1：檢查磁碟使用率

```bash
df -h

```

如果你看到 `/var/lib/elasticsearch` 所在的分區超過 95%，你要先：

- 清舊 index
- 增加磁碟空間
- 或移動 data path

---

# Step 2：刪除舊 index（最常見處理方式）

例如刪除 90 天前的索引：

```bash
curl -X DELETE "http://localhost:9200/filebeat-*-2025.08.*"

```

或手動刪除：

```bash
curl -X DELETE "http://localhost:9200/filebeat-7.7-momo-app-integrity-ap-prod-2025.11.09"

```

💡你可以先查看所有索引大小：

```bash
curl -X GET "http://localhost:9200/_cat/indices?v&h=index,store.size"

```

---

# Step 3：解除 ES 的 read-only 保護

即使磁碟清乾淨了，ES 還是會記住 read-only block  
**要自己取消**

對所有 index：

```bash
curl -X PUT "http://localhost:9200/_all/_settings" -H 'Content-Type: application/json' -d'
{
  "index.blocks.read_only_allow_delete": null
}
'

```

或指定某個 index：

```bash
curl -X PUT "http://localhost:9200/filebeat-7.7-momo-app-integrity-ap-prod-2025.11.09/_settings" \
-H 'Content-Type: application/json' -d'
{
  "index.blocks.read_only_allow_delete": null
}
'

```

---

# Step 4（建議）：調整 ES Watermark 避免一直被鎖

例如：

```bash
curl -X PUT "http://localhost:9200/_cluster/settings" \
-H 'Content-Type: application/json' -d'
{
  "persistent": {
    "cluster.routing.allocation.disk.watermark.low": "80%",
    "cluster.routing.allocation.disk.watermark.high": "90%",
    "cluster.routing.allocation.disk.watermark.flood_stage": "95%",
    "cluster.info.update.interval": "1m"
  }
}
'

```

---

# Step 5（可選）：手動清理 translog

如果 ES 卡住很久，也可以清理 translog：

```bash
curl -X POST "localhost:9200/filebeat-*/_flush"

```

---

# 🔍 最快解法（給你直接用）

1. **刪舊 index**
2. **解除 read-only**

```bash
curl -X PUT "http://localhost:9200/_all/_settings" \
-H 'Content-Type: application/json' -d'
{
  "index.blocks.read_only_allow_delete": null
}
'

```

3. **恢復 Filebeat 寫入**

---

# ✅ 什麼是 Watermark？

Elasticsearch 預設會監控 data 目錄所在磁碟使用率：

<table id="bkmrk-name-%E6%84%8F%E7%BE%A9-%E9%A0%90%E8%A8%AD%E5%80%BC-%E8%A1%8C%E7%82%BA-low-w"><thead><tr><th>Name</th><th>意義</th><th>預設值</th><th>行為</th></tr></thead><tbody><tr><td>**low watermark**</td><td>開始重新 routing shard</td><td>85%</td><td>輕警告</td></tr><tr><td>**high watermark**</td><td>停止將新 shard 分配到該節點</td><td>90%</td><td>停止新增 shard</td></tr><tr><td>**flood-stage watermark**</td><td>**把 index 改成唯讀**</td><td>**95%**</td><td>造成 `read-only-allow-delete`</td></tr></tbody></table>

你的錯誤訊息就是因為 flood-stage 達標。

---

# ✅ 修改 Watermark（官方標準方式）

### 👉 調整 Watermark 百分比

以下 API 會直接套用在整個 cluster：

```bash
curl -X PUT "http://localhost:9200/_cluster/settings" \
-H 'Content-Type: application/json' -d'
{
  "persistent": {
    "cluster.routing.allocation.disk.watermark.low": "80%",
    "cluster.routing.allocation.disk.watermark.high": "90%",
    "cluster.routing.allocation.disk.watermark.flood_stage": "95%",
    "cluster.info.update.interval": "1m"
  }
}
'

```

### 三個參數的用途：

- `"low": "80%"`  
    80% 才開始觸發 routing
- `"high": "90%"`  
    90% 以上 ES 不會在該 node 分配新的 shard
- `"flood_stage": "95%"`  
    超過 95% ES 會鎖 index → read-only

---

# ✅ 調整成更寬鬆（如果磁碟常滿）建議這組：

你可以把 flood\_stage 往上調，比如 98% 或 99%：

```bash
curl -X PUT "http://localhost:9200/_cluster/settings" \
-H 'Content-Type: application/json' -d'
{
  "persistent": {
    "cluster.routing.allocation.disk.watermark.low": "85%",
    "cluster.routing.allocation.disk.watermark.high": "92%",
    "cluster.routing.allocation.disk.watermark.flood_stage": "98%",
    "cluster.info.update.interval": "30s"
  }
}
'

```

💡 **若是單機環境（非 cluster），98% 是最常見的設定。**

---

# ⚠️ 另一種方式：改用「磁碟剩餘容量 (free bytes)」而不是百分比

例如磁碟剩下 10GB 才鎖 index：

```bash
curl -X PUT "http://localhost:9200/_cluster/settings" \
-H 'Content-Type: application/json' -d'
{
  "persistent": {
    "cluster.routing.allocation.disk.watermark.low": "10gb",
    "cluster.routing.allocation.disk.watermark.high": "5gb",
    "cluster.routing.allocation.disk.watermark.flood_stage": "2gb"
  }
}
'

```

這種方式比較直覺、不怕磁碟變大或變小。

---

# 🚨 注意：調整 Watermark 不會解除 index 的 read-only

如果某些 index 已經被鎖，需要再解除：

```bash
curl -X PUT "http://localhost:9200/_all/_settings" \
-H 'Content-Type: application/json' -d'
{
  "index.blocks.read_only_allow_delete": null
}
'

```

或指定某個 index：

```bash
curl -X PUT "http://localhost:9200/filebeat-*/_settings" \
-H 'Content-Type: application/json' -d'
{
  "index.blocks.read_only_allow_delete": null
}
'

```

---

查看所有 index 大小：

```
curl -X GET "http://localhost:9200/_cat/indices?v&h=index,store.size"

```

# 【參數說明】enable_for_single_data_node

下面我會用 **最清楚、最貼近你現在 ES 架構的方式** 解釋  
`enable_for_single_data_node` 是什麼、什麼時候要開/關、會影響什麼。

---

# ⭐ **1. enable\_for\_single\_data\_node 是什麼？**

這是一個 **Elasticsearch 控制 Watermark 是否要啟用於單節點（Single Data Node）環境的設定**。

完整參數名稱：

```
cluster.routing.allocation.disk.watermark.enable_for_single_data_node

```

它的作用：

> **如果你的 Elasticsearch 只有 1 台 node（單機 ES），是否也要套用磁碟 watermark 限制？**

---

# ⭐ **2. 為什麼會有這個設定？**

因為 Elasticsearch 原本的 watermark 行為（low / high / flood\_stage）是為 **多節點 cluster** 設計的。

但如果你是：

✔ 單機 Elasticsearch  
✔ 沒有第二台 node 可以搬 shard

那就會出現很尷尬的情況：

> 一旦磁碟超過 watermark（例如 high 90%）  
> ES 想搬 shard……  
> 但根本沒有其他 node 能搬  
> 然後 ES 就會把 shard 標成 UNASSIGNED  
> 或直接鎖住 index（Read-only）

這在單機 ES 是「不合理且災難級別的行為」。

所以 ES 才加入這個參數：

---

# ⭐ **3. 功能用一句話總結**

### 👉 如果設定成 `true`

即使你只有 1 台 Elasticsearch，也會啟用 watermark（high / flood\_stage 等）。

### 👉 如果設定成 `false`

單機 ES **忽略 high watermark（不阻止分配 shard）**  
但 **flood\_stage（95%）還是會強制生效**（避免你把磁碟寫爆）

---

# ⭐ **4. 預設值是？（重點）**

版本差異：

<table id="bkmrk-elasticsearch-%E7%89%88%E6%9C%AC-%E9%A0%90%E8%A8%AD%E5%80%BC"><thead><tr><th>Elasticsearch 版本</th><th>預設值</th><th>說明</th></tr></thead><tbody><tr><td>**7.x**</td><td>`false`</td><td>單機不啟用 high watermark</td></tr><tr><td>**8.x**</td><td>`true`</td><td>單機也啟用完整 watermark 機制</td></tr></tbody></table>

如果你看到是 `true`，通常代表：

✔ 你是 ES 8.x  
✔ 或你用了 hot/warm/cold/frozen 層級  
✔ 或你用到 searchable snapshots

---

# ⭐ **5. 那你應該設 true 還是 false？（最重要）**

### ✔ 如果你是 **單機 Elasticsearch（只有 1 台）**

我建議：

```
cluster.routing.allocation.disk.watermark.enable_for_single_data_node": false

```

理由：

- 避免 high watermark（90%）直接禁止 shard 分配
- 避免 index 提前 read-only
- 避免 cluster 假性「red/yellow」狀態
- 單機沒必要用 cluster-style routing

> 但 flood\_stage 仍然會保護你避免磁碟爆滿。

這是 **單機 ES 正常、合理、安全** 的設定。

---

### ✔ 如果你是 **真正的多節點 cluster（&gt;= 2 data node）**

你應該保留 `true`  
讓 ES 正常做 shard relocation。

---

# ⭐ **6. 單行解釋（你可以放到備忘）：**

##### `enable_for_single_data_node = false` → 單機不要限制太多，不要亂搬 shard

##### `enable_for_single_data_node = true` → 多機需要完整 watermark 保護

---

# ⭐ 7. 你現在的情況（結論）

根據你前面截圖：  
你有 frozen、watermark、read-only、filebeat 等狀況

**高度機率你是單機 or 少數機器的 ES**

所以：

```
"cluster.routing.allocation.disk.watermark.enable_for_single_data_node": "true"

```

會讓 ES：

❌ 必須搬 shard  
❌ 但沒地方搬  
❌ 於是高水位直接阻斷分配  
❌ 然後 ES 把 index 鎖成 read-only  
❌ 造成 filebeat 錯誤

你就會看到「設定無效」、「read-only-allow-delete」、「flood-stage」等問題。

---

# ⭐ 給你正確的單機 ES 設定模板（最穩定、不會鎖 index）

```bash
PUT _cluster/settings
{
  "persistent": {
    "cluster.routing.allocation.disk.watermark.low": "10gb",
    "cluster.routing.allocation.disk.watermark.high": "5gb",
    "cluster.routing.allocation.disk.watermark.flood_stage": "2gb",

    "cluster.routing.allocation.disk.watermark.enable_for_single_data_node": "false",

    "cluster.routing.allocation.disk.watermark.low.frozen": null,
    "cluster.routing.allocation.disk.watermark.high.frozen": null,
    "cluster.routing.allocation.disk.watermark.flood_stage.frozen": null,
    "cluster.routing.allocation.disk.watermark.low.frozen.max_headroom": null,
    "cluster.routing.allocation.disk.watermark.high.frozen.max_headroom": null,
    "cluster.routing.allocation.disk.watermark.flood_stage.frozen.max_headroom": null
  }
}

```

---

# 如果你願意

貼下面這段，我可以直接判斷你該用 `true` 或 `false`：

```
GET _cat/nodes?v

```

或

```
GET _cluster/settings?include_defaults=true&pretty

```

我可以直接比對你的 cluster 架構（單機/多機）給你最適設定。

# 【常用指令】kibana Dev Tools

[![image-1762845727389.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-11/scaled-1680-/Q1Zup5B1aSXjN3Ph-image-1762845727389.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-11/Q1Zup5B1aSXjN3Ph-image-1762845727389.png)

```shell
#  查看設定
GET /_cluster/settings?include_defaults=true&pretty
```

# Go

# Day 1

go package manager

```
go mod init go_demo
```

func 首字大寫 public  
func 首字小寫 private

# Grafana & Prometheus

# Grafana_install

[https://fanatical-dentist-b1d.notion.site/Grafana-b93a5178e6a64bad886b83fd6bdcf4ea](https://fanatical-dentist-b1d.notion.site/Grafana-b93a5178e6a64bad886b83fd6bdcf4ea)

Enterprise

```INI
[grafana]
name=grafana
baseurl=https://packages.grafana.com/enterprise/rpm
repo_gpgcheck=1
enabled=1
gpgcheck=1
gpgkey=https://packages.grafana.com/gpg.key
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt



```

OSS

```INI
[grafana]
name=grafana
baseurl=https://packages.grafana.com/oss/rpm
repo_gpgcheck=1
enabled=1
gpgcheck=1
gpgkey=https://packages.grafana.com/gpg.key
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
```

install

```shell
sudo yum install grafana

# install zabbix plugin
grafana-cli plugins install alexanderzobnin-zabbix-app
```

# Actuator+Prometheus+Grafana監控視覺化簡介

[https://www.tpisoftware.com/tpu/articleDetails/2446](https://www.tpisoftware.com/tpu/articleDetails/2446)

pom.xml 需增加以下依賴：

```XML
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
```

```INI
#management.endpoint.health.show-details=ALWAYS
#management.endpoints.web.base-path=/monitor
management.endpoints.web.exposure.include=prometheus,metrics
```

- Prometheus 快速入门教程（六）：Spring Boot Actuator 实现应用监控  
    [https://www.cnblogs.com/chanshuyi/p/06\_prometheus\_with\_springboot\_actuator.html](https://www.cnblogs.com/chanshuyi/p/06_prometheus_with_springboot_actuator.html)
- Spring Boot 微服務應用整合Prometheus + Grafana 實現監控告警
    
    [https://juejin.cn/post/6844904052417904653](https://juejin.cn/post/6844904052417904653)
- spring-prometheus的指標意義
    
    [https://blog.csdn.net/ssehs/article/details/123961221](https://blog.csdn.net/ssehs/article/details/123961221)
- prometheus 使用netdata 資料  
    [https://learn.netdata.cloud/docs/exporting-metrics/prometheus#configure-prometheus-to-scrape-netdata-metrics](https://learn.netdata.cloud/docs/exporting-metrics/prometheus#configure-prometheus-to-scrape-netdata-metrics)

# Grafana alerting

https://www.cnblogs.com/liugp/p/17003484.html

# Grafana 下拉選單(prometheus)

[![image-1709185419013.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-02/scaled-1680-/3ai42qyKTzETIpE3-image-1709185419013.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-02/3ai42qyKTzETIpE3-image-1709185419013.png)

prometheus

[![image-1709185498384.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-02/scaled-1680-/M97jqpmykl8A84mR-image-1709185498384.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-02/M97jqpmykl8A84mR-image-1709185498384.png)

prometheus 設定檔，新增自訂label

[![image-1709185606695.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-02/scaled-1680-/VNvJW7urVXNHKC10-image-1709185606695.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-02/VNvJW7urVXNHKC10-image-1709185606695.png)

# Prometheus 相關資源

- [官方範例設定檔](https://github.com/prometheus/prometheus/blob/release-2.50/config/testdata/conf.good.yml)
- [prometheus-book](https://yunlzheng.gitbook.io/prometheus-book/parti-prometheus-ji-chu/alert)
- [prometheus實戰](https://songjiayang.gitbooks.io/prometheus/content/)

# Prometheus PromQL

[![image-1710164090550.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-03/scaled-1680-/iCarZJaclwkXMPXT-image-1710164090550.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-03/iCarZJaclwkXMPXT-image-1710164090550.png)

[![image-1710164264802.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-03/scaled-1680-/Ga8vgsEYE3ymwYsm-image-1710164264802.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-03/Ga8vgsEYE3ymwYsm-image-1710164264802.png)

[![image-1710164314644.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-03/scaled-1680-/Qpjr6qPv2hchWi5f-image-1710164314644.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-03/Qpjr6qPv2hchWi5f-image-1710164314644.png)

[![image-1710164366437.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-03/scaled-1680-/7XXg6uSgUzGsYk7O-image-1710164366437.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-03/7XXg6uSgUzGsYk7O-image-1710164366437.png)

[![image-1710164466436.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-03/scaled-1680-/1Swc4xYQPi2GGlBn-image-1710164466436.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-03/1Swc4xYQPi2GGlBn-image-1710164466436.png)

[![image-1710164634100.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-03/scaled-1680-/mDjlyAKYXHqsw5Pb-image-1710164634100.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-03/mDjlyAKYXHqsw5Pb-image-1710164634100.png)

[![image-1710164902975.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-03/scaled-1680-/f70CxPHHnKTCYCC6-image-1710164902975.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-03/f70CxPHHnKTCYCC6-image-1710164902975.png)

[![image-1710165243618.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-03/scaled-1680-/wYe5BlPGUVQpqS6j-image-1710165243618.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-03/wYe5BlPGUVQpqS6j-image-1710165243618.png)

# Grafana_Promethues docker-compose

```YAML
version: '3.3'

volumes:
  prometheus_data: {}
  grafana_data: {}

networks:
  monitoring:
    driver: bridge

services:
  prometheus:
    image: prom/prometheus
    container_name: prometheus
    restart: always
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./prometheus/:/etc/prometheus/
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/usr/share/prometheus/console_libraries'
      - '--web.console.templates=/usr/share/prometheus/consoles'
    networks:
      - monitoring
    # links:
    #   - alertmanager
    #   - node_exporter
    expose:
      - '9090'
    ports:
      - 9090:9090

  # alertmanager:
  #   image: prom/alertmanager
  #   container_name: alertmanager
  #   restart: always
  #   volumes:
  #     - /etc/localtime:/etc/localtime:ro
  #     - ./alertmanager/:/etc/alertmanager/
  #   command:
  #     - '--config.file=/etc/alertmanager/config.yml'
  #     - '--storage.path=/alertmanager'
  #   networks:
  #     - monitoring
  #   expose:
  #     - '9093'
  #   ports:
  #     - 9099:9093

  # node_exporter 為了能夠采集到主機的運行指標如CPU, 內存，磁盤
  # node_exporter:
  #   image: prom/node-exporter:v0.18.0
  #   container_name: node_exporter
  #   restart: always
  #   volumes:
  #     - /etc/localtime:/etc/localtime:ro
  #     - /proc:/host/proc:ro
  #     - /sys:/host/sys:ro
  #     - /:/rootfs:ro
  #   command: 
  #     - '--path.procfs=/host/proc' 
  #     - '--path.sysfs=/host/sys'
  #     - --collector.filesystem.ignored-mount-points
  #     - "^/(sys|proc|dev|host|etc|rootfs/var/lib/docker/containers|rootfs/var/lib/docker/overlay2|rootfs/run/docker/netns|rootfs/var/lib/docker/aufs)($$|/)"
  #   networks:
  #     - monitoring
  #   expose:
  #     - '9100'

  grafana:
    image: grafana/grafana
    user: "472"
    container_name: grafana
    restart: always
    environment:
      GF_SECURITY_ADMIN_PASSWORD: admin
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./grafana/grafana_data:/var/lib/grafana
      - ./grafana/provisioning/:/etc/grafana/provisioning/
    # env_file:
    #   - ./grafana/config.monitoring
    networks:
      - monitoring
    links:
      - prometheus
    ports:
      - 3000:3000
    depends_on:
      - prometheus



```

```YAML
version: "3.7"
services:
    db:
        image: postgres:13.2-alpine
        restart: always
        environment:
            POSTGRES_DB: postgres
            POSTGRES_USER: postgres #postgres
            POSTGRES_PASSWORD: 12345678 #1234
            PGDATA: /var/lib/postgresql/data
        volumes:
            - ./data/db:/var/lib/postgresql/data
        ports:
            - "5432:5432"
    # pgadmin:
    #     image: dpage/pgadmin4:latest
    #     restart: always
    #     environment:
    #         PGADMIN_DEFAULT_EMAIL: admin@fmt.com.tw #xxx@gmail
    #         PGADMIN_DEFAULT_PASSWORD: 12345678 #abcd
    #         PGADMIN_LISTEN_PORT: 80
    #     ports:
    #         - "5433:80"
    #     volumes:
    #         - ./data/pgadmin-data:/var/lib/pgadmin
    #         - ./data/upload:/tmp/upload
    #     links:
    #         - "db:pgsql-server"
volumes:
    db-data:
    pgadmin-data:
```

# Html

# 【Html】自動重新整理方法

- #### header 加入 meta

```HTML
<meta http-equiv="refresh" content="10">
```

# Javascript

# 【bootstrap-table】相關連結

- [bootstap-table 官網](https://bootstrap-table.com/)
- [bootstrap table filterBy数据刷选过滤器和查询条件 - itxst.com](https://www.itxst.com/bootstrap-table-methods/bjemnemz.html)

# 【Bootstrap-table】bs_table_common.js

```JavaScript

var bootstrapTableBaseConfig = {
    height: getHeight(),
    idField:"dv_no",
    toolbar:"#toolbar",
    minimumCountColumns:2, //最小顯示欄位數量
    pagination:true,
    pageSize:'10000000', //預設row筆數
    pageList:['ALL'],
    queryParams:getQueryParams,
    search:false,
    showColumns:true,
    showFooter:false,
    showPaginationSwitch:true,
    showRefresh:true,
    showExport:true, //匯出按鈕
    showToggle:false,
    detailView:false,
    detailFormatter:detailFormatter,
    // responseHandler:responseHandler,
    virtualScroll:false,
    sidePagination:"server",
    contextMenu: '#bootstraptable-context-menu',
    contextMenuButton: '.bootstraptable-button',
    classes:'table table-bordered table-hover table-sm table-condensed nowrap',
}


/** common function begin */

function ajaxUpdata(config){

    var url = config.url || "";
    var update_type = config.update_type || "更新";
    var update_data = config.update_data || {};
    var ajax_type = config.ajax_type || "POST";
    var ajax_data_type = config.ajax_data_type || "json";
    var after_success_func = config.after_success_func || function(){};
    var after_error_func = config.after_success_func || function(){};
    // var bs_table = config.bs_table || $table;

    console.log('ajaxUpdata_config',config);
    
    var success_func = config.success_func || function(response){
        console.log("response",response);
        if (response.msg != undefined && (response.msg == "success")) {
            if(config.bs_table !== undefined) config.bs_table.bootstrapTable('refresh');
            alert(update_type+"資料成功!!");
        } else {
            alert((response.msg == undefined)?update_type+"失敗請洽系統管理員":response.msg);
        }
        after_success_func();
    }

    var error_func = success_func || function(response){
        var err_msg = response.msg || "";
        alert(err_msg+","+update_type+"失敗請洽系統管理員!!");
        // dialog.close();  //關閉對話視窗
        after_error_func();
    }
    
    var ajax_config = {
                url:  url,
                type: ajax_type,
                data: update_data,
                error: error_func,
                success: success_func,
                dataType: ajax_data_type
                };
    console.log(ajax_config);
    //寫入資料庫
    $.ajax(ajax_config);
}


function getEditDataStr(beforDate,afterData){
    var resultTextArr = [];
    for(var key in beforDate){
        if (afterData[key] !== undefined && beforDate[key] != afterData[key]
        ){
            if (beforDate[key] == null && afterData[key] == '') continue;
            resultTextArr.push(key+":"+beforDate[key]+"=>"+afterData[key]);
        }
    }
    if(resultTextArr.length > 0){
        return resultTextArr.join(",");
    }else{
        return "";
    }
}

function getDataByClass(class_name,perfix){
    var data = {};
    _perfix = perfix || "";
    $('.'+class_name).each(function(){
        var el = this;
        console.log(el.id);
        var id = (el.id).replace(_perfix,'');
        var value = $(el).val();
        data[id] = value;
        
    });
    return data;
}


function initTypeahead(config){
    
    console.log('initTypeahead typeahead',typeof jQuery().typeahead);
    console.log('initTypeahead Bloodhound',typeof Bloodhound);
    //check typeahead exist
    if(typeof jQuery().typeahead !== 'function') return;
    if(typeof Bloodhound !== 'function') return;

    console.log("initTypeahead");

    var selectFunc = (config && config.selectFunc) || function(event, data){
        $("#USER_ID").val(data['USER_ID']);
        $("#USER_NAME").val(data['USER_NAME']);
    }
    var userInfo = new Bloodhound({
        datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
        queryTokenizer: Bloodhound.tokenizers.whitespace,
        remote: {
            url: '/db/php/getOisUser.php?search=%QUERY',
            wildcard: '%QUERY'
        }
    });

    $('#typeahead_input').typeahead({

        	// classNames: {
        	// 	input: 'Typeahead-input',
        	// 	hint: 'Typeahead-hint',
        	// 	selectable: 'Typeahead-selectable'
        	// }
        // },
        hint: true,
        highlight: true,
        minLength: 1
        },
        {
        name: 'best-pictures',
        display: function(data){
            return  data['USER_ID']  ;
            // return  data['USER_ID']  + '-' + data['AD_ID']  + '-' + data['USER_NAME'];
        },
        source: userInfo,
        templates: {
            //查無資料顯示
            empty: [
            '<div class="empty-message">',
                '查無使用者',
            '</div>'
            ].join('\n'),
            
            suggestion: function(data) {
                return '<p><strong>' + data['USER_ID']  + '-' + data['AD_ID']  + '-' + data['USER_NAME'] + ' #' + data['EXT'] + '</strong></p>';
            }
        }
    });

    $('.typeahead').on('typeahead:select', function(event, data) {
        selectFunc(event, data);
    });

    $('.typeahead').on('typeahead:change ', function(event, data) {
        // console.log('change : ' , data);
    });

    $('.js_typeahead_reset_btn').on('click',function(){
        $('.js_typeahead_reset').each(function(index,el){
            $(el).val('');
        });
    });

    // for(var i = 1;i<=5;i++){
    //     if($('.js_typeahead_reset_btn_'+i).length > 0){
    //         $('.js_typeahead_reset_btn_'+i).on('click',function(){
    //             $('.js_typeahead_reset_'+i).each(function(index,el){
    //                 $(el).val('');
    //             });
    //         });
    //     }
    // }
}

function isEmpty(str){
    if(str === undefined || str.trim() === "" || str === null){
        return true;
    }
    return false;
}


function getEditDataStr(beforDate,afterData){
    var resultTextArr = [];
    for(var key in beforDate){
        if (afterData[key] !== undefined && beforDate[key] != afterData[key]
        ){
            if (beforDate[key] == null && afterData[key] == '') continue;
            resultTextArr.push(key+":"+beforDate[key]+"=>"+afterData[key]);
        }
    }
    if(resultTextArr.length > 0){
        return resultTextArr.join(",");
    }else{
        return "";
    }
}
/* ==============================================================================================*/

function responseHandler(res) {
    $.each(res.rows, function (i, row) {
        row.state = $.inArray(row.dv_no, selections) !== -1;
    });
    return res;
}

function getHeight(_height) {
    var _height = _height || 110;
    return $(window).height() - $(".panel-heading").height() - _height;
}

// function getQueryParams(params){

//     var queryParams = {};
//     $(".query").each(function(i,e){
//         var id = (e.id).replace("q_","");
//         queryParams[id]=$(e).val();
//     });
//     $.extend(queryParams,params);
//         console.log("getQueryParams",queryParams);

//     return queryParams;
// }


function getQueryParams(params){
    var queryParams = {};
    $(".query").each(function(i,e){
        var id = (e.id).replace("q_","");
        queryParams[id]=$(e).val();
    });
    $.extend(queryParams,params);
    return queryParams;
}



/* bootsrap table formatter */
function detailFormatter(index, row) {
    var html = [];
    var trans = [];
    $.each(row, function (key, value) {
        if (key != 'total' && key != 'check'){
            var name=(trans[key] === undefined) ? key: trans[key];
            var value=(value === null || value === undefined ) ? '': value;
            html.push('<p><b>' + name + ':</b> ' + value + '</p>');
        }
    });
    return html.join('');
}


function operateFormatter(value, row, index) {

    return [
        '<a class="add" href="javascript:void(0)" title="add">',
        '<i class="glyphicon glyphicon-plus text-success"></i>',
        '</a> ',
        '<a class="edit" href="javascript:void(0)" title="edit">',
        '<i class="glyphicon glyphicon-edit"></i>',
        '</a> ',
        '<a class="remove" href="javascript:void(0)" title="remove">',
        '<i class="glyphicon glyphicon-remove text-danger"></i>',
        '</a>'
    ].join('');
}

function editFormatter(value, row, index){
    return [
    '<a class="edit" href="javascript:void(0)" title="edit">',
    '<i class="glyphicon glyphicon-edit"></i>',
    '</a>'
    ].join('');
}

function yesNoFormatter(value,row,index){
    return (value == 1)?'是':'否';
}

function snFormatter(value,row,index) {
    return index+1;
}

function scanNumberFormatter(data) {
    return (data == 1)?'是':'否';
}

function totalTextFormatter(data) {
    return 'Total';
}

function totalNameFormatter(data) {
    return data.length;
}

function nowrapFormatter(){
    return { css:{"whiie-space":"nowrap"}} ;
}

function totalPriceFormatter(data) {
    var total = 0;
    $.each(data, function (i, row) {
        total += +(row.price.substring(1));
    });
    return '$' + total;
}


 // function buildTable($el) {
        //     var i, j, row,
        //             columns = [],
        //             data = [];
        //     var opt = $el.bootstrapTable('getOptions');
        //     var fixedColumnsOption = { fixedColumns: true,fixedNumber: +$('#fixedNumber').val()};
        //     $.extend(opt,fixedColumnsOption);
        //     console.log("bootstrap_option",opt);
        //     $el.bootstrapTable('destroy').bootstrapTable(opt);
        // }

$(window).resize(function () {
    
    var $table = $table || null;
    if($table !== null){
        
        $table.bootstrapTable('resetView', {
            height: getHeight()
        });
    }
});
```

選項設定預設值

```JavaScript
var DEFAULTS = {
    height: undefined,
    classes: 'table table-bordered table-hover',
    buttons: {},
    theadClasses: '',
    headerStyle: function headerStyle(column) {
      return {};
    },
    rowStyle: function rowStyle(row, index) {
      return {};
    },
    rowAttributes: function rowAttributes(row, index) {
      return {};
    },
    undefinedText: '-',
    locale: undefined,
    virtualScroll: false,
    virtualScrollItemHeight: undefined,
    sortable: true,
    sortClass: undefined,
    silentSort: true,
    sortName: undefined,
    sortOrder: undefined,
    sortReset: false,
    sortStable: false,
    rememberOrder: false,
    serverSort: true,
    customSort: undefined,
    columns: [[]],
    data: [],
    url: undefined,
    method: 'get',
    cache: true,
    contentType: 'application/json',
    dataType: 'json',
    ajax: undefined,
    ajaxOptions: {},
    queryParams: function queryParams(params) {
      return params;
    },
    queryParamsType: 'limit',
    // 'limit', undefined
    responseHandler: function responseHandler(res) {
      return res;
    },
    totalField: 'total',
    totalNotFilteredField: 'totalNotFiltered',
    dataField: 'rows',
    footerField: 'footer',
    pagination: false,
    paginationParts: ['pageInfo', 'pageSize', 'pageList'],
    showExtendedPagination: false,
    paginationLoop: true,
    sidePagination: 'client',
    // client or server
    totalRows: 0,
    totalNotFiltered: 0,
    pageNumber: 1,
    pageSize: 10,
    pageList: [10, 25, 50, 100],
    paginationHAlign: 'right',
    // right, left
    paginationVAlign: 'bottom',
    // bottom, top, both
    paginationDetailHAlign: 'left',
    // right, left
    paginationPreText: '&lsaquo;',
    paginationNextText: '&rsaquo;',
    paginationSuccessivelySize: 5,
    // Maximum successively number of pages in a row
    paginationPagesBySide: 1,
    // Number of pages on each side (right, left) of the current page.
    paginationUseIntermediate: false,
    // Calculate intermediate pages for quick access
    search: false,
    searchHighlight: false,
    searchOnEnterKey: false,
    strictSearch: false,
    regexSearch: false,
    searchSelector: false,
    visibleSearch: false,
    showButtonIcons: true,
    showButtonText: false,
    showSearchButton: false,
    showSearchClearButton: false,
    trimOnSearch: true,
    searchAlign: 'right',
    searchTimeOut: 500,
    searchText: '',
    customSearch: undefined,
    showHeader: true,
    showFooter: false,
    footerStyle: function footerStyle(column) {
      return {};
    },
    searchAccentNeutralise: false,
    showColumns: false,
    showColumnsToggleAll: false,
    showColumnsSearch: false,
    minimumCountColumns: 1,
    showPaginationSwitch: false,
    showRefresh: false,
    showToggle: false,
    showFullscreen: false,
    smartDisplay: true,
    escape: false,
    filterOptions: {
      filterAlgorithm: 'and'
    },
    idField: undefined,
    selectItemName: 'btSelectItem',
    clickToSelect: false,
    ignoreClickToSelectOn: function ignoreClickToSelectOn(_ref) {
      var tagName = _ref.tagName;
      return ['A', 'BUTTON'].includes(tagName);
    },
    singleSelect: false,
    checkboxHeader: true,
    maintainMetaData: false,
    multipleSelectRow: false,
    uniqueId: undefined,
    cardView: false,
    detailView: false,
    detailViewIcon: true,
    detailViewByClick: false,
    detailViewAlign: 'left',
    detailFormatter: function detailFormatter(index, row) {
      return '';
    },
    detailFilter: function detailFilter(index, row) {
      return true;
    },
    toolbar: undefined,
    toolbarAlign: 'left',
    buttonsToolbar: undefined,
    buttonsAlign: 'right',
    buttonsOrder: ['paginationSwitch', 'refresh', 'toggle', 'fullscreen', 'columns'],
    buttonsPrefix: CONSTANTS.classes.buttonsPrefix,
    buttonsClass: CONSTANTS.classes.buttons,
    icons: CONSTANTS.icons,
    iconSize: undefined,
    iconsPrefix: CONSTANTS.iconsPrefix,
    // glyphicon or fa(font-awesome)
    loadingFontSize: 'auto',
    loadingTemplate: function loadingTemplate(loadingMessage) {
      return "<span class=\"loading-wrap\">\n      <span class=\"loading-text\">".concat(loadingMessage, "</span>\n      <span class=\"animation-wrap\"><span class=\"animation-dot\"></span></span>\n      </span>\n    ");
    },
    onAll: function onAll(name, args) {
      return false;
    },
    onClickCell: function onClickCell(field, value, row, $element) {
      return false;
    },
    onDblClickCell: function onDblClickCell(field, value, row, $element) {
      return false;
    },
    onClickRow: function onClickRow(item, $element) {
      return false;
    },
    onDblClickRow: function onDblClickRow(item, $element) {
      return false;
    },
    onSort: function onSort(name, order) {
      return false;
    },
    onCheck: function onCheck(row) {
      return false;
    },
    onUncheck: function onUncheck(row) {
      return false;
    },
    onCheckAll: function onCheckAll(rows) {
      return false;
    },
    onUncheckAll: function onUncheckAll(rows) {
      return false;
    },
    onCheckSome: function onCheckSome(rows) {
      return false;
    },
    onUncheckSome: function onUncheckSome(rows) {
      return false;
    },
    onLoadSuccess: function onLoadSuccess(data) {
      return false;
    },
    onLoadError: function onLoadError(status) {
      return false;
    },
    onColumnSwitch: function onColumnSwitch(field, checked) {
      return false;
    },
    onPageChange: function onPageChange(number, size) {
      return false;
    },
    onSearch: function onSearch(text) {
      return false;
    },
    onToggle: function onToggle(cardView) {
      return false;
    },
    onPreBody: function onPreBody(data) {
      return false;
    },
    onPostBody: function onPostBody() {
      return false;
    },
    onPostHeader: function onPostHeader() {
      return false;
    },
    onPostFooter: function onPostFooter() {
      return false;
    },
    onExpandRow: function onExpandRow(index, row, $detail) {
      return false;
    },
    onCollapseRow: function onCollapseRow(index, row) {
      return false;
    },
    onRefreshOptions: function onRefreshOptions(options) {
      return false;
    },
    onRefresh: function onRefresh(params) {
      return false;
    },
    onResetView: function onResetView() {
      return false;
    },
    onScrollBody: function onScrollBody() {
      return false;
    },
    onTogglePagination: function onTogglePagination(newState) {
      return false;
    }
  };
```

# 【ES6】其他新語法

### 預設值

```JavaScript
function sum(a, b = 1) { // 加入預設值避免錯誤
  return a + b;
}
console.log(sum(1));
```

# 【ES6】物件拷貝

淺層拷貝(只複製一層，內容物件還是參考至同一位址)

```JavaScript
const person = {
  name: '小明',
  obj: {}
}

const person2 = {...person} //方法1
const person3 = Object.assign({},person); //方法2
```

深層拷貝

```JavaScript
const person = {
  name: '小明',
  obj: {}
}

// 使用josn轉成字串，再轉回物件
const person2 = JSON.parse(JSON.stringify(person))
```

# 【ES6】物件拷貝

淺層拷貝(只複製一層，內容物件還是參考至同一位址)

```JavaScript
const person = {
  name: '小明',
  obj: {}
}

const person2 = {...person} //方法1
const person3 = Object.assign({},person); //方法2
```

深層拷貝

```JavaScript
const person = {
  name: '小明',
  obj: {}
}

// 使用josn轉成字串，再轉回物件
const person2 = JSON.parse(JSON.stringify(person))
```

# 【ES6】非同步 與 promise

非同步

```JavaScript
function getData() {
  setTimeout(() => {
    console.log('... 已取得遠端資料');
  }, 0);
}
// 請問取得資料的順序為何
function init() {
  console.log(1);
  getData();
  console.log(2);
}
init();
////////////// result
1
2
... 已取得遠端資料
```

premise 使程式流程同步

```JavaScript
const promiseSetTimeout = (status) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (status) {
        resolve('promiseSetTimeout 成功')
      } else {
        reject('promiseSetTimeout 失敗')
      }
    }, 0);
  })
}

// #2-1 基礎運用 (成功用 then 捕捉, 失敗用 catch捕捉)
// 呼叫成功
console.log(1)
promiseSetTimeout(1)
.then((res)=>{
    console.log(res);
})
.catch((err)=>{
    console.log(err);
})
console.log(2)
/** result
1
promiseSetTimeout 成功
2
*/

// 呼叫失敗
console.log(1)
promiseSetTimeout(2)
.then((res)=>{
    console.log(res);
})
.catch((err)=>{
    console.log(err);
})
console.log(2)
/** result
1
promiseSetTimeout 失敗
2
*/
```

promise 串接 ( 不使用巢狀寫法)

```JavaScript
promiseSetTimeout(1)
.then((res)=>{
    console.log(res);

    // 使用return 發起第二次非同步
    return promiseSetTimeout(1);
})
// 第二次非同步成功
.then((res) => {
    console.log("第二次非同步成功");
})
```

# 【ES6】展開

### 合併陣列

```JavaScript
const groupA = ['小明', '杰倫', '阿姨'];
const groupB = ['老媽', '老爸'];
const groupC = [...groupA,...groupB]; //'小明', '杰倫', '阿姨', '老媽', '老爸'
```

### 繼承物件

```JavaScript
const methods = {
  fn1() {
    console.log(1);
  },
  fn2() {
    console.log(1);
  },
}
const methods2 = {
  ...methods,
  fn3(){
    console.log(1);
  }
}
```

### 轉換為純陣列

```JavaScript
// NodeList 或 HTMLCollection 或 Arguments 是「類陣列」的物件
// 無法使用 map 等純物件的方法
const doms = document.querySelectorAll('li');
console.log(doms); 
// 轉為純陣列
const newDom = [...doms];
newDom.map((item,i)=>{
  console.log(i);
});
```

# 【ES6】陣列方法

[https://www.youtube.com/watch?v=\_vFuDQ\_6Xt8](https://www.youtube.com/watch?v=_vFuDQ_6Xt8)

# 【ES6】箭頭函式

### 改寫原 functoin

```JavaScript
// 傳統寫法
function fn(a, b) {
  return a * b;
}
// 箭頭函式
const fn = (a, b) => {
  return a * b;
}
// 省略 return
const fn = (a, b) => {
  a * b;
}
/////////////////////////////
function fn(name) {
  return "Hello"+name;
}
// 單行省略 return , {}
const fn = (name) => "Hello "+name;

```

### this 指向不會指向全域，而是指向自己

```JavaScript
var name = '全域'
const person = {
  name: '小明',
  callName: function () { 
    console.log('1', this.name); // 1 小明
    setTimeout(function () {
      console.log('2', this.name); // 2 全域
      console.log('3', this); // 3 window
    }, 10);
  },
}
```

# 【ES6】賦值解構

### 取出特定值作為變數

```JavaScript
// #1 取出特定值作為變數
const person = {
  name: '小明',
  age: 16,
  like: '鍋燒意麵'
}
const {name, age} = person;
console.log(name, age); // 小明, 16
```

### 陣列解構

<div id="bkmrk-"><div></div></div>```shell
const arr = ['小明', '杰倫', '漂亮阿姨'];
const [Ming, Jay ] = arr;
console.log(Ming, Jay); // '小明', '杰倫'
```

### 解構加展開

```JavaScript
const people = {
  Ming: {
    name: '小明',
    age: 16,
    like: '鍋燒意麵'
  },
  Jay: {
    name: '杰倫',
    age: 18,
    like: '跑車'
  },
  Auntie: {
    name: '漂亮阿姨',
    age: 22,
    like: '名牌包'
  }
}
const { Ming,...other} = people;
console.log(Ming, other );
// Ming
// {
//     name: '小明',
//     age: 16,
//     like: '鍋燒意麵'
// }

// other
// {
//   Jay: {
//     name: '杰倫',
//     age: 18,
//     like: '跑車'
//   },
//   Auntie: {
//     name: '漂亮阿姨',
//     age: 22,
//     like: '名牌包'
//   }
// }
```

### 解構加上重新命名

```JavaScript
// 冒號：別名
const { Ming:AAA,...other} = people;
console.log(AAA, other );
```

### 函式參數解構

```JavaScript
const person = {name: "Sam", age: 18};

const fn = function({name, age}){
console.log(name,age) 
}

fn(person); // Sam 18
```

### 解構在React應用

###  

```JavaScript

const { useState } = React;
```

# 【ES6】async / await

### 使用方式

```JavaScript
// #1 當資料有順序性的時候
const fn1 = () =>{
  axios.get("https://randomuser.me/api/")
  .then((res)=> {
    console.log("fn1 seed",res.data.info.seed);
    const seed = res.data.info.seed;
    return axios.get(`https://randomuser.me/api/?seed=${seed}`)
  })
  .then((res)=>{
    console.log("fn1 seed2",res.data.info.seed)
  })
}

// #2 使用 async await 來修改
const fn2 = async ()=> {
  const res = await axios.get("https://randomuser.me/api/")
  const seed = res.data.info.seed;
  console.log("fn2 seed",res.data.info.seed);
  const res2 = await axios.get(`https://randomuser.me/api/?seed=${seed}`);
  const seed2 = res2.data.info.seed;
  console.log("fn2 seed2",res2.data.info.seed);
}


// #3 立即函式的寫法
(async function(){
  const res = await axios.get("https://randomuser.me/api/")
  const seed = res.data.info.seed;
  console.log("fn3 seed",res.data.info.seed);
  const res2 = await axios.get(`https://randomuser.me/api/?seed=${seed}`);
  const seed2 = res2.data.info.seed;
  console.log("fn3 seed2",res2.data.info.seed);
})();


```

### 錯誤處理

```JavaScript
const fn4 = async () => {
  try {
    const res = await axios.get("https://randomuser.me/api/")
    return res
  } catch (error) {
    throw error
  }
}

fn4()
.then((res)=>{
  console.log(res);
})
.catch((err)=>{
  console.log(err);
});
```

### async function 本身就是 promise

[![image-1674748127619.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-01/scaled-1680-/MPzDB9hd4GsVTixg-image-1674748127619.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-01/MPzDB9hd4GsVTixg-image-1674748127619.png)

### async Function 與 React

```JavaScript

// async Function 與 React
const App = () => {
  const [data, setData] = React.useState({}) 

  React.useEffect(() => {
    (async () => {
      const res = await axios.get('https://jsonplaceholder.typicode.com/todos/1');
      console.log(res);
      setData(res.data);
    })();
  }, []);

  return <div>
    {data.title}
  </div>
}

```

# 【ES6】ESModual

[![image-1674749911584.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-01/scaled-1680-/EuEw8ikkjfuNOjuR-image-1674749911584.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-01/EuEw8ikkjfuNOjuR-image-1674749911584.png)

### 什麼樣的匯出對應什麼樣的匯入

#### 預設匯入(一個匯出檔只能有一個)

<div id="bkmrk-%E5%B8%B8%E8%A6%8B%E7%9A%84%E5%8C%AF%E5%87%BA%E6%96%B9%E5%BC%8F%EF%BC%8C%E9%80%9A%E5%B8%B8%E7%94%A8%E6%96%BC%E5%8C%AF%E5%87%BA%E7%89%A9%E4%BB%B6%EF%BC%8C%E5%9C%A8-r"><div>常見的匯出方式，通常用於匯出物件，在 React 開發中可用來匯出<span style="background-color: #ffff00;">元件</span></div><div></div></div>```JavaScript
/** 匯出頁 exportDefault.js */
export default function fn(){
	console.log("default export")
}

/** 匯入 index.html */
/** export ${name}(隨便取) from ${path} */
<script type="module">
	export aa from "./exportDefault.js";
	aa();
</script>
```

```JavaScript
/** 匯出頁 exportDefault.js */
export default {
	name: "sam",
    func: function(){
    	console.log("預設方法...")
    }
}

/** 匯入 index.html */
/** import ${name}(隨便取) from ${path} */
<script type="module">
	import aa from "./exportDefault.js";
	console.log(aa.name);
    aa.func();
</script>
```

#### 具名匯入(可多個)

<div id="bkmrk-%E5%8F%AF%E7%94%A8%E6%96%BC%E5%8C%AF%E5%87%BA%E5%B7%B2%E5%AE%9A%E7%BE%A9%E7%9A%84%E8%AE%8A%E6%95%B8%E3%80%81%E7%89%A9%E4%BB%B6%E3%80%81%E5%87%BD%E5%BC%8F%EF%BC%8C%E5%B0%88%E6%A1%88"><div>可用於匯出已定義的變數、物件、函式，專案開發中通常用於 “方法匯出”</div><div>第三方的框架、函式、套件很常使用具名定義 “<span style="background-color: #ffff00;">方法</span>”</div></div>```JavaScript
/** 匯出頁 exportModule.js */
export const myName = "Jhon"
export function fn1() {
	console.log("function 1");
}

/** 匯入 index.html */
/** import { 解構名稱 } from ${path} */
<script type="module">
	import {fn1, myName} from "./exportDefault.js";
    console.log("myName",myName);
	fn1();
</script>
```

#### 全部匯出(不常用)

```JavaScript
/** 匯出頁 exportModule.js */
export const myName = "Jhon"
export function fn1() {
	console.log("function 1");
}

/** 匯入 index.html */
/** import * as ${別名} from ${path} */
<script type="module">
	import * as bb from "./exportDefault.js";
    console.log("myName",bb.myName);
	bb.fn1();
</script>
```

#### sideEffect (舊函式庫，立即執行)

```JavaScript
/** 匯出頁 sideEffect.js */
(function(global){
	global.$ = "I am jquery";
})(window)

/** 匯入 index.html */
/** import ${path} */
<script type="module">
	import "./sideEffect.js";
    console.log("$",$); // I am jquery
	bb.fn1();
</script>
```

# 【ES6】import / export

匯入的類型要看匯出的形式，所以我用匯入方式來說明

### 使用default 匯出

匯入就可以取任何名稱

```JavaScript
import fn from './func.js';
fn();
// 匯出檔案 func.js
// export default function (){
//   console.log("fn")
// }
```

### 具名匯入

匯入必須使用完整名稱，也可以匯入的時候給予別名

```JavaScript
import {fn2, fn3 as new_fn3, person} from './func.js';
fn2();
new_fn3();
export const person = {name:"sam",age:18}

// 匯出檔案 func.js
// export function fn2(){
//   console.log("fn2")
// }
// export function fn3(){
//   console.log("fn3")
// }

```

### 全部匯入

```JavaScript
import * as obj from './func.js';
obj.fn();
// 匯出檔案 func.js
// export function fn(){
//   console.log("fn")
// }
```

### 傳統lib匯入

ex.Jquery

```JavaScript
// import file
import './fn.js';

// module file
(function() {
  console.log('IIFE');
})();

```

# 【JS】相關資源

## 教學

---

- [现代 JavaScript 教程](https://zh.javascript.info/)

# 【JS】iframe 自適應內容高度

```JavaScript
<script type="text/javascript">
function SetCwinHeight() {
    var iframeid = document.getElementById("mainframe"); //iframe id  
    if (document.getElementById) {
        if (iframeid && !window.opera) {
            if (iframeid.contentDocument && iframeid.contentDocument.body.offsetHeight) {
                iframeid.height = iframeid.contentDocument.body.offsetHeight;
            } else if (iframeid.Document && iframeid.Document.body.scrollHeight) {
                iframeid.height = iframeid.Document.body.scrollHeight;
            }
        }
    }
}
</script>
```

```HTML
<iframe src="./sourcePage.html" name="mainframe" id="mainframe"
    width="100%" marginwidth="0" marginheight="0"
    scrolling="No" frameborder="0"
    onload="Javascript:SetCwinHeight()">
</iframe>
```

# 【ES6】null 判斷

[https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/Optional\_chaining](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/Optional_chaining)

**可選串連**運算子 **`?.`** 允許進行深層次的物件值存取，而無需透過明確的物件值串連驗證。`?.` 運算子的操作與 `.` 屬性存取運算子相似，後者會在參照到 [nullish](https://developer.mozilla.org/en-US/docs/Glossary/Nullish "此頁面目前僅提供英文版本") ([`null`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/null "此頁面目前僅提供英文版本") or [`undefined`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined "此頁面目前僅提供英文版本")) 的值時出現錯誤，而前者可選串連則回傳 `undefined` 。 當需要存取一個函數，而這函數並不存在時，則會回傳 `undefined`

```JavaScript
const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah',
  },
};

const dogName = adventurer.dog?.name;
console.log(dogName);
// Expected output: undefined

console.log(adventurer.someNonExistentMethod?.());
// Expected output: undefined

```

當一個物件 `obj` 是巢狀結構時，在沒有可選串連之下，要去查找一深層的屬性值需要驗證每層間的參照連結：

```JavaScript
let nestedProp = obj.first && obj.first.second && obj.first.second.third;
```

現在可以用?.做串接判斷

```JavaScript
let nestedProp = obj.first?.second?.third
```

在用 ?? 賦予預設值避免 undefined

```JavaScript
let nestedProp = obj.first?.second?.third ?? "default"
```

# 【Tool】常用方法

\*判斷為空

```JavaScript
const isEmpty = (val) => {
    // null or undefined
    if (val == null) return true;

    if (typeof val === "boolean") return false;

    if (typeof val === "number") return !val;

    if (val instanceof Error) return val.message === "";

    // eslint-disable-next-line default-case
    switch (Object.prototype.toString.call(val)) {
        // String or Array
        case "[object String]":
        case "[object Array]":
            return !val.length;

        // Map or Set or File
        case "[object File]":
        case "[object Map]":
        case "[object Set]": {
            return !val.size;
        }
        // Plain Object
        case "[object Object]": {
            return !Object.keys(val).length;
        }
    }

    return false;
};

const isNotEmpty = (val) => {
    return !isEmpty(val);
};
```

# 【JS】?? 與  ||  比較

在 JavaScript 中，`??` (Nullish Coalescing) 和 `||` (Logical OR) 看起來很像，都是用來提供**預設值**的運算子，但它們判斷「什麼時候該用預設值」的標準完全不同。

這篇教學將幫你釐清兩者的核心差異，並告訴你什麼時候該用哪一個。

<div aria-busy="false" aria-live="polite" id="bkmrk-"><div aria-busy="false" aria-live="polite">---

</div></div>## 核心差異：判斷標準

兩者的主要區別在於對於「虛值 (Falsy values)」的處理方式。

### 1. 邏輯或運算子 `||` (Logical OR)

只要左側的值轉換為布林值後是 false，就會回傳右側的值。

JavaScript 中的 Falsy 值包括：

<div aria-busy="false" aria-live="polite" id="bkmrk-null-undefined-0-%22%22-"><div aria-busy="false" aria-live="polite">- `null`
- `undefined`
- `0`
- `""` (空字串)
- `NaN`
- `false`

</div></div>### 2. 空值合併運算子 `??` (Nullish Coalescing)

只有當左側的值是 null 或 undefined 時，才會回傳右側的值。

它會保留 0、"" 和 false 等有效數據。

<div aria-busy="false" aria-live="polite" id="bkmrk--0"><div aria-busy="false" aria-live="polite">---

</div></div>## 對比表

<table border="1" id="bkmrk-%E7%89%B9%E6%80%A7-%7C%7C-%28or%29-%3F%3F-%28nulli"><thead><tr><th>特性</th><th>|| (OR)</th><th>?? (Nullish)</th></tr></thead><tbody><tr><td>**觸發預設值的條件**</td><td>任何 Falsy 值 (0, "", false...)</td><td>僅限 null 或 undefined</td></tr><tr><td>**對 0 的處理**</td><td>視為 False，會被預設值覆蓋</td><td>視為有效值，保留 0</td></tr><tr><td>**對 "" (空字串) 的處理**</td><td>視為 False，會被預設值覆蓋</td><td>視為有效值，保留 ""</td></tr><tr><td>**設計目的**</td><td>廣泛的邏輯備案</td><td>精確處理「缺失資料」</td></tr></tbody></table>

<div aria-busy="false" aria-live="polite" id="bkmrk--1"><div aria-busy="false" aria-live="polite">---

</div></div>## 實戰範例

假設我們正在開發一個使用者設定頁面：

<div aria-busy="false" aria-live="polite" id="bkmrk-javascript-const-use"><div aria-busy="false" aria-live="polite"><div data-hveid="0" data-ved="0CAAQhtANahcKEwjVvbzE0eGRAxUAAAAAHQAAAAAQWA"><div><span class="ng-tns-c3121828532-21">JavaScript</span><div><button aria-label="複製代碼" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger copy-button ng-tns-c3121828532-21 mat-unthemed ng-star-inserted"></button></div></div><div><div>```
<span class="hljs-keyword">const</span> userSettings = {
  <span class="hljs-attr">speed</span>: <span class="hljs-number">0</span>,
  <span class="hljs-attr">nickname</span>: <span class="hljs-string">""</span>,
  <span class="hljs-attr">isPremium</span>: <span class="hljs-literal">false</span>,
  <span class="hljs-attr">theme</span>: <span class="hljs-literal">null</span>
};

<span class="hljs-comment">// --- 使用 || 的結果 ---</span>
<span class="hljs-built_in">console</span>.log(userSettings.speed || <span class="hljs-number">10</span>);      <span class="hljs-comment">// 輸出: 10 (即便 0 是有效的數值)</span>
<span class="hljs-built_in">console</span>.log(userSettings.nickname || <span class="hljs-string">"匿名"</span>); <span class="hljs-comment">// 輸出: "匿名" (即便空字串是有效的名字)</span>
<span class="hljs-built_in">console</span>.log(userSettings.isPremium || <span class="hljs-literal">true</span>);  <span class="hljs-comment">// 輸出: true (這會讓使用者的 false 設定失效)</span>

<span class="hljs-comment">// --- 使用 ?? 的結果 ---</span>
<span class="hljs-built_in">console</span>.log(userSettings.speed ?? <span class="hljs-number">10</span>);      <span class="hljs-comment">// 輸出: 0 (正確保留了數值)</span>
<span class="hljs-built_in">console</span>.log(userSettings.nickname ?? <span class="hljs-string">"匿名"</span>); <span class="hljs-comment">// 輸出: "" (正確保留了空字串)</span>
<span class="hljs-built_in">console</span>.log(userSettings.isPremium ?? <span class="hljs-literal">true</span>);  <span class="hljs-comment">// 輸出: false (正確保留了布林值)</span>
<span class="hljs-built_in">console</span>.log(userSettings.theme ?? <span class="hljs-string">"Light"</span>);  <span class="hljs-comment">// 輸出: "Light" (因為是 null)</span>

```

</div></div></div>---

</div></div>## 使用場景建議

### 什麼時候使用 `??`？ (推薦做法)

當你想要\*\*「安全地提供預設值」\*\*且不希望意外覆蓋掉 `0`、`false` 或 `""` 時。

<div aria-busy="false" aria-live="polite" id="bkmrk-%E8%99%95%E7%90%86-api-%E5%9B%9E%E5%82%B3%E7%9A%84%E9%81%B8%E6%93%87%E6%80%A7%E5%8F%83%E6%95%B8%E3%80%82-%E6%95%B8%E5%80%BC%E8%A8%88"><div aria-busy="false" aria-live="polite">- 處理 API 回傳的選擇性參數。
- 數值計算（例如：計數器、座標）。
- UI 表單輸入（允許空字串）。

</div></div>### 什麼時候使用 `||`？

當你想要\*\*「只要沒內容就給預設值」\*\*，或者你確定資料只會是字串/物件，且你不需要處理 `0` 或空字串的情況。

<div aria-busy="false" aria-live="polite" id="bkmrk-%E8%88%8A%E7%89%88%E7%80%8F%E8%A6%BD%E5%99%A8%E7%9B%B8%E5%AE%B9%E6%80%A7%EF%BC%88%3F%3F-%E6%98%AF-es2020"><div aria-busy="false" aria-live="polite">- 舊版瀏覽器相容性（`??` 是 ES2020 引入的）。
- 確保變數一定有「實質內容」（非空、非零）。

---

</div></div>## 注意事項：混合使用

在 JavaScript 中，出於運算優先級的安全性考慮，你**不能**在沒有括號的情況下直接混合使用 `&&`、`||` 與 `??`：

<div aria-busy="false" aria-live="polite" id="bkmrk-javascript-%2F%2F-%E2%9D%8C-%E9%8C%AF%E8%AA%A4%E8%AA%9E%E6%B3%95"><div aria-busy="false" aria-live="polite"><div data-hveid="0" data-ved="0CAAQhtANahcKEwjVvbzE0eGRAxUAAAAAHQAAAAAQWQ"><div><span class="ng-tns-c3121828532-22">JavaScript</span><div><button aria-label="複製代碼" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger copy-button ng-tns-c3121828532-22 mat-unthemed ng-star-inserted"></button></div></div><div><div>```
<span class="hljs-comment">// ❌ 錯誤語法：SyntaxError</span>
<span class="hljs-comment">// const result = a || b ?? c;</span>

<span class="hljs-comment">// ✅ 正確語法：使用括號明確優先級</span>
<span class="hljs-keyword">const</span> result = (a || b) ?? c;

```

</div></div></div></div></div>> **總結建議：** 在現代開發中，為了避免處理數值 `0` 或布林值時出現非預期的 Bug，**優先考慮使用 `??`**。

需要我為你示範如何將現有的舊代碼從 `||` 重構成 `??` 嗎？

# Jenkins

# 【Jenkins】相關連結

#### Jenkins入门 pipeline (官方文檔)  
[https://www.jenkins.io/zh/doc/book/pipeline/syntax/](https://www.jenkins.io/zh/doc/book/pipeline/syntax/)  
[https://www.jenkins.io/doc/book/pipeline/](https://www.jenkins.io/doc/book/pipeline/)

- [使用 Shared Libraries](https://jenkins.xfoss.com/pipeline/shared-libraries.html)  
    [Getting Started With Shared Libraries in Jenkins](https://www.youtube.com/watch?v=Wj-weFEsTb0)  
    [Using Resource Files From a Jenkins Shared Library](https://www.youtube.com/watch?v=eV7roTXrEqg)
- [pipline語法](https://jenkins.xfoss.com/pipeline/syntax.html)  
    [What Is the Difference Between Scripted and Declarative Pipeline](https://www.youtube.com/watch?v=GJBlskiaRrI)

##### 【udemy】‎持續集成（Continuous Integration with Jenkins）---從初學到精通‎  
[Jenkins.pdf](https://bookstack.treemanou.com/attachments/6)

##### 【Docker Hub】Jenkins 官說明  
[Jenkins - Official Image | Docker Hub](https://hub.docker.com/_/jenkins?tab=description)

##### Jenkins+Github觸發器  
[Jenkins : GitHub Plugin (jenkins-ci.org)](https://wiki.jenkins-ci.org/display/JENKINS/GitHub+Plugin)

##### PMD Jenkins plugin:

[https://wiki.jenkins-ci.org/display/JENKINS/PMD+Plugin](https://wiki.jenkins-ci.org/display/JENKINS/PMD+Plugin)

##### Findbugs Jenkins Plugin:

[https://wiki.jenkins-ci.org/display/JENKINS/FindBugs+Plugin](https://wiki.jenkins-ci.org/display/JENKINS/FindBugs+Plugin)

---

[Postman+Newman+Git+Jenkins介面自動化測試 | IT人 (iter01.com)](https://iter01.com/509070.html)

---

##### Jenkins pipeline

[\[Day 27\] Jenkins (1) - iT 邦幫忙::一起幫忙解決難題，拯救 IT 人的一天 (ithome.com.tw)](https://ithelp.ithome.com.tw/articles/10209290)

##### 在 Jenkins 容器中執行 docker 指令

[Jenkins 容器中執行 docker 指令：叡揚部落格 (gss.com.tw)](https://www.gss.com.tw/blog/jenkins-docker)

```shell
docker run -d --name test-jenkins \
--user root -p 8080:8080 \
-p 50000:50000 \
-v /var/run/docker.sock:/var/run/docker.sock \
jenkins/jenkins
```

```shell
docker exec -it --user root test-jenkins /bin/bash
```

```shell
apt-get update && apt-get -y install apt-transport-https ca-certificates \
curl gnupg2 software-properties-common && \
curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg > /tmp/dkey; \
apt-key add /tmp/dkey && \
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") $(lsb_release -cs) stable" && \
apt-get update && apt-get -y install docker-ce
```

# 【Jenkins】常用plug-in

[![image-1645453937611.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-02/scaled-1680-/NDo1lzUd8bclFdTC-image-1645453937611.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-02/NDo1lzUd8bclFdTC-image-1645453937611.png)

[![image-1645453953637.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-02/scaled-1680-/HuZpcr5h7b7RFYlo-image-1645453953637.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-02/HuZpcr5h7b7RFYlo-image-1645453953637.png)

[![image-1645454072914.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-02/scaled-1680-/V92kUXgbXeE2jfR6-image-1645454072914.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-02/V92kUXgbXeE2jfR6-image-1645454072914.png)

[![image-1740016958350.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-02/scaled-1680-/M6SF0iNZnhooi25b-image-1740016958350.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-02/M6SF0iNZnhooi25b-image-1740016958350.png)

權限控管  
https://plugins.jenkins.io/matrix-auth/

# 【Jenkins】Jenkinsfile

[Using a Jenkinsfile](https://www.jenkins.io/doc/book/pipeline/jenkinsfile/)

```JavaScript
pipeline {
    agent any
    stages{
        stage('Init'){
            steps {
                echo "Testing......"
            }
        }
 	stage('Build'){
            steps {
                echo "Building......"
            }
        }
 	stage('Deploy'){
            steps {
                echo "Code Deployed."
            }
        }
    }
}
```

```JavaScript
pipeline {
    agent any

    tools{
        maven 'local maven'
    }

    parameters{
        string(name: 'tomcat_dev', defaultValue: '1.1.1.1', description: 'Staging Server')
        string(name: 'tomcat_prod', defaultValue: '2.2.2.2', description: 'Production Server')
    }

    triggers {
         pollSCM('* * * * *')
     }

     stages{
        stage('Build'){
            steps {
                sh 'mvn clean package'
            }
            post {
                success {
                    echo '开始存档...'
                    archiveArtifacts artifacts: '**/target/*.war'
                }
            }
        }

     stage ('Deployments'){
            parallel{
                stage ('Deploy to Staging'){
                    steps {
                        sh "scp -i /Users/gaoyan/Documents/SunnyDemo/dev/tomcat-demo.pem.txt **/target/*.war ec2-user@${params.tomcat_dev}:/var/lib/tomcat8/webapps"
                    }
                }

                stage ("Deploy to Production"){
                    steps {
                        sh "scp -i /Users/gaoyan/Documents/SunnyDemo/dev/tomcat-demo.pem.txt **/target/*.war ec2-user@${params.tomcat_prod}:/var/lib/tomcat8/webapps"
                    }
                }
            }
        }
    }
}
```

- [\[Day 25\] Pipeline 專案 (上) - iT 邦幫忙::一起幫忙解決難題，拯救 IT 人的一天 (ithome.com.tw)](https://ithelp.ithome.com.tw/articles/10195957)
- [\[Day 26\] Pipeline 專案 (中) - iT 邦幫忙::一起幫忙解決難題，拯救 IT 人的一天 (ithome.com.tw)](https://ithelp.ithome.com.tw/articles/10196116)
- [\[Day 27\] Pipeline 專案 (下) - iT 邦幫忙::一起幫忙解決難題，拯救 IT 人的一天 (ithome.com.tw)](https://ithelp.ithome.com.tw/articles/10196252)

---

**問題1**：\*\*\\target\\\*.war: No such file or directory

解決辦法：

Jenkins無法根據正則匹配找到war文件，我們需要在Jenkinsfile中指定到war文件的完整路徑，例如/var/jenkins\_home/workspace/package\_pipelpine/webapp/target/\*.war(更改成你機器上的實際路徑)。而不是\*\*\\target\\\*.war

---

<div id="bkmrk-%E5%A6%82%E6%9E%9C%E5%87%BA%E7%8F%BE%E9%9D%A2%E4%B8%8B%E9%8C%AF%E8%AA%A4%E6%8F%90%E7%A4%BA%EF%BC%9A-%5Bdeploy-"><div><div><div><div><div><div><div><div><div data-purpose="curriculum-item-viewer-content"><section aria-label="第 7 章節：Jenkins Pipeline As Code (Jenkinsfile)，第 58 堂講座：問題解決：完全自動化Jenkins Pipeline-代碼執行" class="lecture-view--container--pL22J">如果出現面下錯誤提示：

\[Deploy to Production\] Host key verification failed.  
\[Deploy to Production\] lost connection

解決思路如下：

請改用如下命令

1\.  
scp -o StrictHostKeyChecking=no -i /Users/gaoyan/Documents/SunnyDemo/dev/tomcat-demo.pem.txt webapp/target/webapp.war ec2-user@52.15.183.253:/var/lib/tomcat8/webapps

加了-o StrictHostKeyChecking=no參數， 這樣主機密鑰就會在連接時自動加入到known\_hosts中去

2\.

命令行也用加了-o參數的命令手動登錄試試

ssh -o StrictHostKeyChecking=no ec2-user@13.58.179.83 -i tomcat-demo.pem.txt

這樣主機密鑰就會在連接時自動加入到known\_hosts中去

3.保證用jenkins執行的時候 tomcat-demo.pem.txt這個文件其他用戶對他有操作權，但是注意執行上面第二個命令時，會提示其他人不能對這個文件有訪問權，所以用sudo chmod 700 tomcat-demo.pem.txt， 執行完這個命令以後，再把權限改成 644， sudo chmod 644 tomcat-demo.pem.txt， 然後去jenkins執行第一條命令

4.還不成功刪除known\_hosts試試， cd~/.ssh

rm known\_hosts

如果出現：

Permissions 0644 for 'jenkinskey.pem' are too open.  
It is required that your private key files are NOT accessible by others.  
This private key will be ignored.  
Load key "jenkinskey.pem": bad permissions

解決辦法：

chmod 400 pem\_filename.pem

</section></div></div></div></div></div></div></div></div></div></div>

# 【GitLab】push 觸發pipline

 Gitlab 專案的 settings -&gt;integrations

 設定 【url】【tocken】(見下方jenkins 設定)

[![image-1684293361508.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-05/scaled-1680-/DQhO3f4d6cNpjRhb-image-1684293361508.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-05/DQhO3f4d6cNpjRhb-image-1684293361508.png)

點選下方【 Test】【Push events】測試

[![image-1684294348365.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-05/scaled-1680-/xpqKw4LkFse6ie6Z-image-1684294348365.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-05/xpqKw4LkFse6ie6Z-image-1684294348365.png)

 成功上方會出現藍色200字樣

[![image-1684293624304.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-05/scaled-1680-/ixilIFKAL84M1mmF-image-1684293624304.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-05/ixilIFKAL84M1mmF-image-1684293624304.png)

### Jenkins (Url, token)

[![image-1684293986220.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-05/scaled-1680-/tKykSiaHfRuZ5pKz-image-1684293986220.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-05/tKykSiaHfRuZ5pKz-image-1684293986220.png)

[![image-1684294044948.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-05/scaled-1680-/gLu5gmXTCFV7Z0JY-image-1684294044948.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-05/gLu5gmXTCFV7Z0JY-image-1684294044948.png)

[![image-1684294112288.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-05/scaled-1680-/s2GfdPUTSYH7d89u-image-1684294112288.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-05/s2GfdPUTSYH7d89u-image-1684294112288.png)

# 【Jenkins】docker-compose

```YAML
version: '3.1'

services:

  jenkins:
    deploy:
      resources:
        limits:
          cpus: '0.50'
          # memory: 500M
    image: jenkins/jenkins:2.462.3-lts-jdk11
    # image: jenkins/jenkins:lts-jdk11
    # restart: always
    container_name: com_jenkins
    environment:
      - TZ=Asia/Taipei #設定時區
    volumes:
      - ./data/jenkins:/var/jenkins_home
      # - /var/run/docker.sock:/var/run/docker.sock
    ports:
      - 8096:8080
      - 8097:50000      
    networks:
      - jenkins 
networks:
  jenkins:

# /var/jenkins_home/secrets/initialAdminPassword
```

# 【Jenkins】如何執行排程作業

- [How to Schedule a Jenkins Job to Run Every Hour](https://www.youtube.com/watch?v=JhvVJtYFUm0)
- [Jenkins排程作業](https://jenkins.xfoss.com/pipeline/running.html)

1. Jenkins 作業可以手動執行，可以由某個Web 鉤子觸發，也可以定時運行；
2. 定時執行Jenkins 作業，是經由「宣告式指令產生器」 中的「triggers: Triggers」 指令，「cron: Build periodically」完成的；
3. 這裡的`cron`與\*nix 中`cron`有略微差異，包括了`H`及`@midnight`等語法，其中`H`指`hash`，可避免在某個時刻過多的作業（作業競爭），`@midnight`這樣的寫法也可以避免作業競爭；
4. `TZ=TZ=Asia/Taipei`這種寫法可以給Jenkins`cron`加入時區；
5. 將聲明式指令產生器產生的程式碼片段：

```Fortran
pipeline {
    agent any
	environment {
        TZ = "Asia/Taipei"
    }
    triggers {
        cron '* * * * *'
    }
    stages {
      stage('demo') {
        steps {
        // do....
        }
      }
    }
}
```

# 【Jenkins】執行git專案上的pipline

參考：[https://ithelp.ithome.com.tw/m/articles/10287403](https://ithelp.ithome.com.tw/m/articles/10287403)

- Definition: `Pipeline script from SCM`
- SCM: `Git`
- Repository URL: `https://github.com/ben4932042/jenkins-ithome.git`
- Branch Specifier (blank for 'any'): `main`
- **<span style="color: #ff0000;">Script Path</span>**: `jobs/ithome-day3-sample-job/Jenkinsfile` =&gt;<span style="color: #ff0000;"> pipline 檔案</span>

[![image-1742440086893.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-03/scaled-1680-/rdW2ZbC7Ob5XVFUg-image-1742440086893.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-03/rdW2ZbC7Ob5XVFUg-image-1742440086893.png)

[![image-1742440093950.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-03/scaled-1680-/bKmjP7SZeqiwEjth-image-1742440093950.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-03/bKmjP7SZeqiwEjth-image-1742440093950.png)

# 【Jenkins】 on macOS 速查表

## 1. 安裝流程

```bash
# 更新 Homebrew 自身與套件清單
brew update

# （可選）升級已安裝的套件
brew upgrade

# 安裝 Jenkins LTS（穩定版）
brew install jenkins-lts

```

---

## 2. 啟動 / 停止 / 重啟 Jenkins

```bash
# 啟動 Jenkins 服務（開機自動啟動）
brew services start jenkins-lts

# 停止 Jenkins 服務
brew services stop jenkins-lts

# 重啟 Jenkins 服務
brew services restart jenkins-lts

# 手動啟動（非守護行程模式，終端機關掉就結束）
jenkins-lts

```

---

## 3. Jenkins 預設資訊

- **預設 Port**: `8080`  
    👉 瀏覽器打開 [http://localhost:8080](http://localhost:8080/)
- **初始管理員密碼**（第一次登入需要）：
    
    ```bash
    cat ~/.jenkins/secrets/initialAdminPassword
    
    ```

---

## 4. 常見檢查

```bash
# 檢查 Jenkins 是否有在跑
curl http://localhost:8080

# 查看 Jenkins log
tail -f /usr/local/var/log/jenkins.log

```

---

## 5. 升級 Jenkins

```bash
# 更新 Homebrew 清單
brew update

# 升級 Jenkins
brew upgrade jenkins-lts

# 重啟服務讓新版生效
brew services restart jenkins-lts

```

---

## 6. 移除 Jenkins

```bash
brew services stop jenkins-lts
brew uninstall jenkins-lts

```

---

📌 建議：

- **第一次安裝後**，記得把初始密碼存起來，避免找不到。
- **長期使用**：定期跑 `brew update && brew upgrade`，保持 Jenkins 最新。

---

要不要我再幫你整理一份 **對照表格**（指令 → 功能），方便你 copy 貼到筆記？

# K8s

# 相關資源

### Kubernetes in Action

[https://fanatical-dentist-b1d.notion.site/Kubernetes-in-Action-8ac92f08a3fd41028ed1ae20cdc7a007](https://fanatical-dentist-b1d.notion.site/Kubernetes-in-Action-8ac92f08a3fd41028ed1ae20cdc7a007)

### Kubernetes in Action2

[https://fanatical-dentist-b1d.notion.site/Kubernetes-in-Action2-4d565efc45124bc989484c8cdb7df181](https://fanatical-dentist-b1d.notion.site/Kubernetes-in-Action2-4d565efc45124bc989484c8cdb7df181)

### Kubernetes 官方文檔(簡中)

[https://kubernetes.io/zh-cn/docs/home/](https://kubernetes.io/zh-cn/docs/home/)

### Kubernetes 考試相關

[https://kubernetes.io/docs/reference/kubectl/overvie](https://kubernetes.io/docs/reference/kubectl/overview/)[w/](https://kubernetes.io/docs/reference/kubectl/overview/)  
[https://kubernetes.io/docs/reference/kubectl/cheatsheet](https://kubernetes.io/docs/reference/kubectl/cheatsheet)/

# Kind 簡易說明

## **K8s Workload**

[![image-1735010531186.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/hwk1XdvpyG7CuWKk-image-1735010531186.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/hwk1XdvpyG7CuWKk-image-1735010531186.png)

[![image-1735010627965.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/roU8JO1qSZLqreXE-image-1735010627965.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/roU8JO1qSZLqreXE-image-1735010627965.png)

以下是 **DaemonSet**、**StatefulSet**、**ReplicaSet** 和 **Deployment** 的詳細比較：

<table id="bkmrk-%E7%89%B9%E6%80%A7-daemonset-statefu"><thead><tr><th>**特性**</th><th>**DaemonSet**</th><th>**StatefulSet**</th><th>**ReplicaSet**</th><th>**Deployment**</th></tr></thead><tbody><tr><td>**用途**</td><td>確保每個 Node 上運行一個 Pod 副本。</td><td>部署有狀態的應用，每個 Pod 都有固定標識。</td><td>確保指定數量的 Pod 副本運行。</td><td>管理 Pod 和 ReplicaSet，提供滾動更新功能。</td></tr><tr><td>**典型場景**</td><td>日誌收集器、監控代理等需要全節點覆蓋。</td><td>資料庫、分佈式系統等需要穩定標識或存儲的應用。</td><td>確保無狀態應用的高可用性。</td><td>部署和管理無狀態應用，支持版本控制和更新。</td></tr><tr><td>**Pod 名稱**</td><td>名稱可變，與 Node 關聯，無固定格式。</td><td>有固定名稱（如 `db-0`, `db-1`）。</td><td>無固定格式，與 Deployment 類似。</td><td>通常由 Deployment 自動生成，不直接管理 Pod。</td></tr><tr><td>**存儲**</td><td>不涉及持久化存儲，通常為臨時存儲。</td><td>每個 Pod 可關聯專屬持久存儲。</td><td>通常無需存儲，支持臨時存儲。</td><td>通常無需存儲，支持無狀態應用。</td></tr><tr><td>**縮放（副本數）**</td><td>無法指定副本數，與 Node 數量相關。</td><td>支持指定副本數，且保證順序和標識穩定。</td><td>支持指定副本數，所有副本平等。</td><td>支持指定副本數，自動調整和滾動更新。</td></tr><tr><td>**更新方式**</td><td>滾動更新，逐一更新節點上的 Pod。</td><td>滾動更新，有序處理 Pod，確保依賴順序。</td><td>滾動更新，副本數量保持穩定。</td><td>滾動更新，支持回滾和藍綠部署策略。</td></tr><tr><td>**依賴的資源**</td><td>與 Node 關聯，不依賴其他資源。</td><td>通常依賴持久化存儲（如 PVC）。</td><td>通常不依賴其他資源。</td><td>自動創建和管理 ReplicaSet。</td></tr><tr><td>**伸縮性**</td><td>固定與 Node 數量匹配，無法手動調整。</td><td>可靈活擴展，並保證 Pod 的穩定標識。</td><td>手動指定副本數，無狀態，副本之間無區別。</td><td>自動管理副本數，支持水平和垂直擴展。</td></tr><tr><td>**依賴順序**</td><td>Pod 啟動順序無依賴。</td><td>Pod 啟動和刪除有順序，確保依賴關係。</td><td>無順序依賴，隨機啟動和刪除。</td><td>無順序依賴，通常關注應用級別的管理。</td></tr><tr><td>**高可用性**</td><td>和 Node 數量相關，通常與節點數一致。</td><td>通過固定標識和穩定的存儲保證高可用性。</td><td>通過多副本保證高可用性，但不管理應用版本。</td><td>提供高可用性並支持應用升級與版本回滾。</td></tr></tbody></table>

---

### **適用場景總結**

1. **DaemonSet**：
    
    
    - 用於需要在所有 Node 上部署的服務，例如： 
        - 日誌收集器（如 Fluentd）。
        - 監控代理（如 Prometheus Node Exporter）。
        - 網絡插件（如 Calico, Weave）。
2. **StatefulSet**：
    
    
    - 適用於需要穩定標識和存儲的有狀態應用，例如： 
        - 資料庫（如 MySQL, PostgreSQL）。
        - 分布式系統（如 Kafka, ZooKeeper）。
        - 需要保持 Pod 順序的應用。
3. **ReplicaSet**：
    
    
    - 用於確保固定數量的無狀態 Pod 副本運行，例如： 
        - 部署應用的核心部分，但一般由 Deployment 自動管理。
4. **Deployment**：
    
    
    - 最常用於管理無狀態應用，支持滾動更新、版本控制和自動擴展，例如： 
        - Web 應用（如 Nginx, Node.js）。
        - 微服務應用的部署。

這樣的比較有助於釐清這些資源的功能和適用情境，幫助在實際項目中選擇合適的解決方案。

在 Kubernetes 中，`Service` 的 `type` 定義了服務的訪問方式和範圍。以下是 Kubernetes 支持的四種類型及其用途說明：

---

## Service 的四種Type

### [![image-1735011766578.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/Vf0uxFIwmKlWsrjt-image-1735011766578.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/Vf0uxFIwmKlWsrjt-image-1735011766578.png)

### 1. **ClusterIP (預設類型)**

- **說明**： 
    - 僅限於集群內部的虛擬 IP，用於服務發現和內部通信。
    - 外部無法直接訪問。
- **用途**： 
    - 用於內部微服務之間的通信。
    - 通常搭配 DNS（如 `kube-dns`）進行名稱解析。
- **範例**：

```yaml
apiVersion: v1
kind: Service
metadata:
  name: clusterip-service
spec:
  type: ClusterIP
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

```

---

### 2. **NodePort**

- **說明**： 
    - 將服務暴露在每個節點的固定端口上（範圍：30000-32767）。
    - 用戶可以通過 `<NodeIP>:<NodePort>` 訪問服務。
- **用途**： 
    - 測試環境中，用於將服務暴露給集群外部的用戶。
    - 生產環境中通常搭配 **Ingress** 或 **LoadBalancer**。
- **範例**：

```yaml
apiVersion: v1
kind: Service
metadata:
  name: nodeport-service
spec:
  type: NodePort
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
      nodePort: 30007

```

---

### 3. **LoadBalancer**

- **說明**： 
    - 在雲提供商（如 GCP、AWS 或 Azure）中創建外部負載均衡器，並將流量轉發到 Service。
    - 每個 LoadBalancer 類型的 Service 都會自動獲得一個外部 IP。
- **用途**： 
    - 用於公開服務給互聯網，用於生產環境。
    - 支持流量負載均衡。
- **範例**：

```yaml
apiVersion: v1
kind: Service
metadata:
  name: loadbalancer-service
spec:
  type: LoadBalancer
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

```

---

### 4. **ExternalName**

- **說明**： 
    - 將 Service 名稱映射到外部 DNS 名稱。
    - 不會創建 ClusterIP，也無法進行內部流量轉發。
- **用途**： 
    - 將 Kubernetes 內部服務請求代理到集群外部的服務。
- **範例**：

```yaml
apiVersion: v1
kind: Service
metadata:
  name: externalname-service
spec:
  type: ExternalName
  externalName: external.example.com

```

---

### **服務類型對比**

<table id="bkmrk-%E9%A1%9E%E5%9E%8B-%E6%98%AF%E5%90%A6%E5%B0%8D%E5%A4%96%E6%9A%B4%E9%9C%B2-%E8%A8%AA%E5%95%8F%E6%96%B9%E5%BC%8F-%E5%85%B8%E5%9E%8B%E4%BD%BF%E7%94%A8%E5%A0%B4"><thead><tr><th>**類型**</th><th>**是否對外暴露**</th><th>**訪問方式**</th><th>**典型使用場景**</th></tr></thead><tbody><tr><td>**ClusterIP**</td><td>否</td><td>集群內部通信，使用虛擬 IP</td><td>微服務之間的內部通信。</td></tr><tr><td>**NodePort**</td><td>是</td><td>`<NodeIP>:<NodePort>`</td><td>測試環境或簡單的外部訪問。</td></tr><tr><td>**LoadBalancer**</td><td>是</td><td>自動分配外部 IP</td><td>公開服務到互聯網，支持負載均衡。</td></tr><tr><td>**ExternalName**</td><td>是（間接）</td><td>使用外部 DNS 名稱解析</td><td>將內部服務請求代理到集群外部服務。</td></tr></tbody></table>

---

### 適用場景建議

1. **ClusterIP**：適合內部微服務的互相調用，默認類型。
2. **NodePort**：適合小型測試環境，簡單公開服務。
3. **LoadBalancer**：適合生產環境，需要高可用的負載均衡服務。
4. **ExternalName**：適合需要訪問外部資源但想使用 Kubernetes 內部 DNS 管理的情況。

這些 `type` 結合不同的網絡需求，靈活應對各種應用場景。

---

## YML 的各種 Kind

<table id="bkmrk-kind-%E8%AA%AA%E6%98%8E-%E7%94%A8%E9%80%94-cronjob-%E5%AE%9A" style="width: 809px; height: 687px;"><thead><tr style="height: 29px;"><th style="width: 205px; height: 29px;">**Kind**</th><th style="width: 234px; height: 29px;">**說明**</th><th style="width: 370px; height: 29px;">**用途**</th></tr></thead><tbody><tr style="height: 46px;"><td style="width: 205px; height: 46px;">**Pod**</td><td style="width: 234px; height: 46px;">Kubernetes 中最小的執行單位，包含容器。</td><td style="width: 370px; height: 46px;">部署單個應用程式容器。</td></tr><tr style="height: 29px;"><td style="width: 205px; height: 29px;">**Job**</td><td style="width: 234px; height: 29px;">執行一次性任務，確保成功完成。</td><td style="width: 370px; height: 29px;">運行批處理任務。</td></tr><tr style="height: 29px;"><td style="width: 205px; height: 29px;">**CronJob**</td><td style="width: 234px; height: 29px;">定期執行的任務。</td><td style="width: 370px; height: 29px;">排程任務（如每天備份數據）。</td></tr><tr style="height: 29px;"><td style="width: 205px; height: 29px;">**StatefulSet**</td><td style="width: 234px; height: 29px;">管理有狀態的應用。</td><td style="width: 370px; height: 29px;">部署需固定標識或穩定存儲的 Pod。</td></tr><tr style="height: 29px;"><td style="width: 205px; height: 29px;">**ReplicaSet**</td><td style="width: 234px; height: 29px;">確保指定數量的 Pod 始終運行。</td><td style="width: 370px; height: 29px;">通常由 Deployment 管理，用於維持 Pod 副本數。</td></tr><tr style="height: 46px;"><td style="width: 205px; height: 46px;">**DaemonSet**</td><td style="width: 234px; height: 46px;">確保每個 Node 上運行一個 Pod 副本。</td><td style="width: 370px; height: 46px;">部署監控代理、日誌收集器等。</td></tr><tr style="height: 46px;"><td style="width: 205px; height: 46px;">**Deployment**</td><td style="width: 234px; height: 46px;">管理 Pod 和 ReplicaSet，支持滾動更新。</td><td style="width: 370px; height: 46px;">部署無狀態應用，保持指定數量的 Pod 運行。</td></tr><tr style="height: 29px;"><td style="width: 205px; height: 29px;">**Service**</td><td style="width: 234px; height: 29px;">定義 Pod 的網絡訪問規則。</td><td style="width: 370px; height: 29px;">將外部流量路由到 Pod，提供負載均衡。</td></tr><tr style="height: 29px;"><td style="width: 205px; height: 29px;">**Ingress**</td><td style="width: 234px; height: 29px;">提供 HTTP/HTTPS 路由規則。</td><td style="width: 370px; height: 29px;">公開訪問服務，支持負載均衡和基於域名的路由。</td></tr><tr style="height: 29px;"><td style="width: 205px; height: 29px;">**NodePort** </td><td style="width: 234px; height: 29px;">將外部流量映射到指定節點端口。</td><td style="width: 370px; height: 29px;">將集群外部訪問直接映射到內部 Service，通常為開發測試用途。</td></tr><tr style="height: 29px;"><td style="width: 205px; height: 29px;">**ConfigMap**</td><td style="width: 234px; height: 29px;">存儲非機密配置數據。</td><td style="width: 370px; height: 29px;">將配置注入 Pod（環境變量或掛載文件）。</td></tr><tr style="height: 46px;"><td style="width: 205px; height: 46px;">**Secret**</td><td style="width: 234px; height: 46px;">存儲機密數據（如密碼、API 密鑰）。</td><td style="width: 370px; height: 46px;">安全地將敏感信息注入 Pod。</td></tr><tr style="height: 29px;"><td style="width: 205px; height: 29px;">**PersistentVolume (PV)**</td><td style="width: 234px; height: 29px;">定義持久化存儲。</td><td style="width: 370px; height: 29px;">提供存儲資源，獨立於 Pod 的生命週期。</td></tr><tr style="height: 46px;"><td style="width: 205px; height: 46px;">**PersistentVolumeClaim (PVC)**</td><td style="width: 234px; height: 46px;">申請 PersistentVolume。</td><td style="width: 370px; height: 46px;">Pod 使用 PVC 來請求持久存儲資源。</td></tr><tr style="height: 29px;"><td style="width: 205px; height: 29px;">**Namespace**</td><td style="width: 234px; height: 29px;">將資源分隔成不同的邏輯組。</td><td style="width: 370px; height: 29px;">支持多租戶環境。</td></tr><tr style="height: 46px;"><td style="width: 205px; height: 46px;">**Role 和 ClusterRole**</td><td style="width: 234px; height: 46px;">定義訪問權限。</td><td style="width: 370px; height: 46px;">管控資源的讀寫權限，`Role` 限於 Namespace，`ClusterRole` 全集群。</td></tr><tr style="height: 46px;"><td style="width: 205px; height: 46px;">**RoleBinding 和 ClusterRoleBinding**</td><td style="width: 234px; height: 46px;">綁定訪問權限到用戶。</td><td style="width: 370px; height: 46px;">授權用戶或服務帳號訪問權限。</td></tr><tr style="height: 46px;"><td style="width: 205px; height: 46px;">**HorizontalPodAutoscaler (HPA)**</td><td style="width: 234px; height: 46px;">根據指標自動調整 Pod 副本數量。</td><td style="width: 370px; height: 46px;">提高應用的彈性擴展能力。</td></tr></tbody></table>

---

### 範例 YAML 說明

#### **1. CronJob**

執行每天凌晨 12 點的數據備份：

```yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-backup
spec:
  schedule: "0 0 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: backup
            image: backup-tool:latest
            args:
            - /bin/sh
            - -c
            - "backup-script.sh"
          restartPolicy: OnFailure

```

---

#### **2. DaemonSet**

在每個 Node 上部署日誌收集器：

```yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: log-collector
spec:
  selector:
    matchLabels:
      app: log-collector
  template:
    metadata:
      labels:
        app: log-collector
    spec:
      containers:
      - name: log-collector
        image: log-collector:latest

```

---

#### **3. Deployment**

部署一個無狀態 Web 應用，副本數為 3：

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
      - name: web
        image: nginx:latest
        ports:
        - containerPort: 80

```

---

#### **4. Job**

執行一次性任務：

```yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: one-time-task
spec:
  template:
    spec:
      containers:
      - name: task-runner
        image: busybox
        args:
        - /bin/sh
        - -c
        - "echo Hello, Kubernetes!"
      restartPolicy: OnFailure

```

---

#### **5. StatefulSet**

部署有狀態的數據庫應用：

```yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: database
spec:
  serviceName: "database-service"
  replicas: 3
  selector:
    matchLabels:
      app: database
  template:
    metadata:
      labels:
        app: database
    spec:
      containers:
      - name: db
        image: mysql:5.7
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "password"
        ports:
        - containerPort: 3306

```

---

#### **6. Pod**

部署一個單一容器的應用：

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: single-app
spec:
  containers:
  - name: app
    image: busybox
    args:
    - /bin/sh
    - -c
    - "echo Running a single pod!"

```

#### **1. CronJob**

執行每天凌晨 12 點的數據備份：

```yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-backup
spec:
  schedule: "0 0 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: backup
            image: backup-tool:latest
            args:
            - /bin/sh
            - -c
            - "backup-script.sh"
          restartPolicy: OnFailure

```

---

#### **2. Service**

定義一個 Service，將流量路由到 Pod：

```yaml
apiVersion: v1
kind: Service
metadata:
  name: web-app-service
spec:
  selector:
    app: web-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: ClusterIP

```

- **說明**: 這個 Service 將把進入集群內部的流量路由到標籤 `app: web-app` 的 Pod。

###  

#### **NodePort 範例**

以下是一個簡單的 **NodePort** 配置範例，用於將集群外部的請求轉發到內部的 Pod：

```yaml
apiVersion: v1
kind: Service
metadata:
  name: nodeport-service
spec:
  type: NodePort
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80         # ClusterIP 的服務端口
      targetPort: 8080 # Pod 內部容器的端口
      nodePort: 30007  # 節點上的固定外部端口（30000~32767 之間）

```

- **說明**： 
    - **`type: NodePort`**：指定此服務為 NodePort 類型。
    - **`nodePort`**：設置固定的外部端口，供外部用戶通過 `<NodeIP>:30007` 訪問服務。
    - **適用場景**：開發測試環境或簡單的外部訪問，不建議用於生產環境，因為其功能有限且缺乏靈活性。

---

# kubectl常用命令

<div id="bkmrk-"><div><div><div><div><svg class="icon-md" fill="none" height="41" role="img" viewbox="0 0 41 41" width="41" xmlns="http://www.w3.org/2000/svg"></svg></div></div></div></div></div>### `kubectl` 常用指令表格（繁體中文）

以下是 **`kubectl` 常用指令**的表格整理，包含了集群資訊查看、資源管理、排錯調試、日誌查看等各類常用操作，方便你快速查找和使用。

<div id="bkmrk-%E5%8A%9F%E8%83%BD%E5%88%86%E9%A1%9E-%E6%8C%87%E4%BB%A4-%E8%AA%AA%E6%98%8E-%E9%9B%86%E7%BE%A4%E8%B3%87%E8%A8%8A-kube"><div><div><div><div><div data-message-author-role="assistant" data-message-id="578a1dc8-c91e-497c-aea1-84316329ce05" data-message-model-slug="gpt-4o"><div><div>---

<table><thead><tr><th>**功能分類**</th><th>**指令**</th><th>**說明**</th></tr></thead><tbody><tr><td>**集群資訊**</td><td>`kubectl version`</td><td>查看客戶端和伺服器的版本資訊</td></tr><tr><td> </td><td>`kubectl cluster-info`</td><td>查看集群的基本資訊</td></tr><tr><td> </td><td>`kubectl get nodes`</td><td>列出所有節點（Nodes）</td></tr><tr><td> </td><td>`kubectl describe node <node>`</td><td>查看指定節點的詳細資訊</td></tr><tr><td>**查看資源**</td><td>`kubectl get pods`</td><td>查看目前命名空間的所有 Pods</td></tr><tr><td> </td><td>`kubectl get pods -n <namespace>`</td><td>查看指定命名空間的 Pods</td></tr><tr><td> </td><td>`kubectl get services`</td><td>查看目前命名空間的所有 Services</td></tr><tr><td> </td><td>`kubectl get deployments`</td><td>查看目前命名空間的所有 Deployments</td></tr><tr><td> </td><td>`kubectl get all`</td><td>查看目前命名空間的所有資源</td></tr><tr><td>**查看詳細資訊**</td><td>`kubectl describe pod <pod>`</td><td>查看 Pod 的詳細資訊</td></tr><tr><td> </td><td>`kubectl describe svc <service>`</td><td>查看 Service 的詳細資訊</td></tr><tr><td>**創建資源**</td><td>`kubectl apply -f <file.yaml>`</td><td>使用 YAML 文件創建或更新資源</td></tr><tr><td> </td><td>`kubectl create deployment`</td><td>創建一個新的 Deployment</td></tr><tr><td>**更新資源**</td><td>`kubectl edit <resource> <name>`</td><td>直接編輯現有資源</td></tr><tr><td>**刪除資源**</td><td>`kubectl delete pod <pod>`</td><td>刪除指定的 Pod</td></tr><tr><td> </td><td>`kubectl delete -f <file.yaml>`</td><td>根據 YAML 文件刪除資源</td></tr><tr><td>**查看日誌**</td><td>`kubectl logs <pod>`</td><td>查看指定 Pod 的日誌</td></tr><tr><td> </td><td>`kubectl logs <pod> -f`</td><td>即時追蹤 Pod 的日誌</td></tr><tr><td> </td><td>`kubectl logs <pod> --tail=100`</td><td>查看最近 100 行的 Pod 日誌</td></tr><tr><td>**執行指令**</td><td>`kubectl exec -it <pod> -- bash`</td><td>進入 Pod 的終端</td></tr><tr><td> </td><td>`kubectl exec <pod> -- <command>`</td><td>在 Pod 中執行指定的指令</td></tr><tr><td>**排錯與調試**</td><td>`kubectl describe pod <pod>`</td><td>查看 Pod 的詳細資訊，用於排查錯誤</td></tr><tr><td> </td><td>`kubectl get events`</td><td>查看集群的事件日誌</td></tr><tr><td> </td><td>`kubectl top node`</td><td>查看節點的資源使用狀況</td></tr><tr><td> </td><td>`kubectl top pod`</td><td>查看 Pod 的資源使用狀況</td></tr><tr><td>**命名空間管理**</td><td>`kubectl get ns`</td><td>查看所有命名空間</td></tr><tr><td> </td><td>`kubectl create namespace <ns>`</td><td>創建新的命名空間</td></tr><tr><td> </td><td>`kubectl delete namespace <ns>`</td><td>刪除指定的命名空間</td></tr><tr><td>**配置管理**</td><td>`kubectl config view`</td><td>查看當前的 kubeconfig 配置</td></tr><tr><td> </td><td>`kubectl config get-contexts`</td><td>查看可用的上下文（Contexts）</td></tr><tr><td> </td><td>`kubectl config use-context <ctx>`</td><td>切換至指定的上下文</td></tr><tr><td>**YAML 輸出**</td><td>`kubectl get <resource> -o yaml`</td><td>將資源輸出為 YAML 格式</td></tr><tr><td>**監控資源**</td><td>`kubectl get pods --watch`</td><td>即時監控 Pod 的狀態變化</td></tr><tr><td>**滾動更新**</td><td>`kubectl rollout status <deploy>`</td><td>查看 Deployment 的滾動更新狀態</td></tr><tr><td> </td><td>`kubectl rollout undo <deploy>`</td><td>回滾 Deployment 至上一版本</td></tr></tbody></table>

</div></div></div></div></div></div></div></div><div id="bkmrk--0"><div><div><div><div data-message-author-role="assistant" data-message-id="578a1dc8-c91e-497c-aea1-84316329ce05" data-message-model-slug="gpt-4o"><div><div>---

</div></div></div></div></div></div></div>```
kubectl create deployment nginx --image=nginx
kubectl expose deployment --port=80 --type=NodePort
kubectl get pod,svc
kubectl get cs
kubectl get pods
kubectl apply -f
```

# yaml 說明

```YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.15
        ports:
        - containerPort: 80

```

[![image-1736738210438.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/CNmxJMCOoM45fJcN-image-1736738210438.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/CNmxJMCOoM45fJcN-image-1736738210438.png)

<table id="bkmrk-%E6%AC%84%E4%BD%8D%E5%90%8D%E7%A8%B1-%E8%AA%AA%E6%98%8E-apiversion-a"><thead><tr><th>**欄位名稱**</th><th>**說明**</th></tr></thead><tbody><tr><td>apiVersion</td><td>API 版本</td></tr><tr><td>kind</td><td>資源類型</td></tr><tr><td>metadata</td><td>資源元數據</td></tr><tr><td>spec</td><td>資源規格</td></tr><tr><td>replicas</td><td>副本數量</td></tr><tr><td>selector</td><td>標籤選擇器</td></tr><tr><td>template</td><td>Pod 模板</td></tr><tr><td>metadata</td><td>Pod 元數據</td></tr><tr><td>spec</td><td>Pod 規格</td></tr><tr><td>containers</td><td>容器配置</td></tr></tbody></table>

使用kubectl create 生成 yaml 文件

```
kubectl create deployment web --image=nginx -o yaml --dry-run > my1.yaml
```

使用kubectl get 導出 yaml 文件

```
kubectl get deploy niginx -o=yaml --export > my2.yaml
```

# Pod 基本概念

[![image-1736906344006.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/XuFXzqHaaBDN1rFy-image-1736906344006.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/XuFXzqHaaBDN1rFy-image-1736906344006.png)

[![image-1736906405688.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/Cm8wfrdUpGALUOkz-image-1736906405688.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/Cm8wfrdUpGALUOkz-image-1736906405688.png)

### Pod 概述

Pod 是 Kubernetes 系統中可以創建和管理的最小單位，是資源對象模型中由使用者創建或部署的最小資源對象模型，也是 Kubernetes 平台上運行容器化應用的資源對象。其他的資源對象都是用來支援或強化 Pod 的功能，例如：

- **控制器** 資源對象用來監控 Pod 的狀態
- **Service** 或 **Ingress** 資源對象用來暴露 Pod 引用外部訪問
- **PersistentVolume** 資源對象用來為 Pod 提供存儲等功能

Kubernetes 並不直接調度容器，而是調度 Pod。Pod 是由一個或多個容器組成的。

Pod 是 Kubernetes 的**最重要概念**。每一個 Pod 都有一個特殊的被稱為 **Pause 容器** 的容器。Pause 容器的鏡像對應於 Kubernetes 平台的一部分，負責維護 Pod 的網路命名空間及其他元數據。  
除了 Pause 容器，每個 Pod 還包含一個或多個具備業務邏輯的使用者業務容器。

### 1. Pod 基本概念

<div id="bkmrk-%E6%9C%80%E5%B0%8F%E9%83%A8%E7%BD%B2%E5%96%AE%E4%BD%8D-%E5%8C%85%E5%90%AB%E5%A4%9A%E5%80%8B%E5%AE%B9%E5%99%A8%EF%BC%88%E5%AE%B9%E5%99%A8%E9%9B%86%E5%90%88%EF%BC%89-"><div><div><div><div data-message-author-role="assistant" data-message-id="0059456a-b824-4214-af32-9c9d8353149a" data-message-model-slug="gpt-4o"><div><div>1. 最小部署單位
2. 包含多個容器（容器集合）
3. 一個 Pod 中的容器共享網路命名空間
4. Pod 是短暫的（不永久存在）

</div></div></div></div></div></div></div>### 2. Pod 存在意義

1. **創建容器使用 Docker**
    
    
    - 一個 Docker 對應一個容器
    - 一個容器有進程
    - 一個容器運行一個應用程序
2. **Pod 是多進程設計，運行多個應用程序**
    
    
    - 一個 Pod 有多個容器
    - 一個容器裡面運行一個應用程序
3. **Pod 存在為了親密性應用**
    
    
    - 兩個應用之間進行交互
    - 網路之間調用
    - 兩個應用需要頻繁調用

### 3. Pod 實現機制

1. **共享網路**
2. **共享存儲**

#### 詳細說明：

- **共享網路**：  
    通過 Pause 容器，將其他業務容器加入到 Pause 容器裡面，讓所有業務容器在同一個命名空間中，可以實現網路共享。
- **共享存儲**：  
    引入數據卷的概念（Volume），使用數據卷進行持久化存儲。

[![image-1736749367514.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/5iXb5J1ckYCPWBGh-image-1736749367514.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/5iXb5J1ckYCPWBGh-image-1736749367514.png)

以下是圖片中的文字辨識及繁體中文翻譯：

---

### 4.鏡像拉取策略

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: nginx
      image: nginx:1.14
      imagePullPolicy: Always

```

#### 鏡像拉取策略說明：

- **IfNotPresent**：預設值，當宿主機上沒有該鏡像時才會拉取。
- **Always**：每次創建 Pod 時都會重新拉取一次鏡像。
- **Never**：Pod 永遠不會主動拉取該鏡像。

---

以下是圖片中的文字辨識及翻譯成繁體中文：

---

### 5.Pod 資源限制示例

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
    - name: db
      image: mysql
      env:
        - name: MYSQL_ROOT_PASSWORD
          value: "password"
      resources:
        requests:
          memory: "64Mi"
          cpu: "250m"
        limits:
          memory: "128Mi"
          cpu: "500m"

```

#### Pod 和 Container 的資源請求與限制說明：

- **`spec.containers[].resources.limits.cpu`**：容器的 CPU 使用上限。
- **`spec.containers[].resources.limits.memory`**：容器的記憶體使用上限。
- **`spec.containers[].resources.requests.cpu`**：容器的 CPU 請求值。
- **`spec.containers[].resources.requests.memory`**：容器的記憶體請求值。

---

以下是圖片中的文字辨識及繁體中文翻譯：

---

### 6.Pod 重啟策略

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: dns-test
spec:
  containers:
    - name: busybox
      image: busybox:1.28.4
      args:
        - /bin/sh
        - -c
        - sleep 36000
  restartPolicy: Never

```

#### 重啟策略說明：

- **Always**：當容器終止退出後，**總是重啟容器**。這是預設的策略。
- **OnFailure**：當容器異常退出（退出狀態碼非 0）時，**才重啟容器**。
- **Never**：當容器終止退出後，**不會重啟容器**。

---

以下是圖片中的文字辨識及繁體中文翻譯：

---

### 7.Pod 健康檢查

```yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
    name: liveness-exec
spec:
  containers:
    - name: liveness
      image: busybox
      args:
        - /bin/sh
        - -c
        - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy
      livenessProbe:
        exec:
          command:
            - cat
            - /tmp/healthy
        initialDelaySeconds: 5
        periodSeconds: 5

```

---

#### 健康檢查說明：

- **livenessProbe（存活檢查）**
    
    
    - 如果檢查失敗，將**殺死容器**，根據 Pod 的 `restartPolicy` 來操作。
- **readinessProbe（就緒檢查）**
    
    
    - 如果檢查失敗，Kubernetes 會**將 Pod 從 Service 的 endpoints 中刪除**。
    - 以下是 **`initialDelaySeconds`** 和 **`periodSeconds`** 的詳細說明：
        
        ---
        
        ### **簡單範例說明：**
        
        ```yaml
        initialDelaySeconds: 5
        periodSeconds: 5
        
        ```
        
        這表示：
        
        
        1. Pod 啟動後等待 5 秒才開始執行健康檢查。
        2. 之後每隔 5 秒執行一次健康檢查。
        
        ---
        
        ### 🔎 **其他常見健康檢查參數：**
        
        <table><thead><tr><th>參數名稱</th><th>說明</th></tr></thead><tbody><tr><td>`initialDelaySeconds`</td><td>首次檢查前的延遲時間</td></tr><tr><td>`periodSeconds`</td><td>每次檢查的間隔時間</td></tr><tr><td>`timeoutSeconds`</td><td>每次健康檢查的**超時時間**</td></tr><tr><td>`successThreshold`</td><td>判定為**成功**所需的連續檢查次數</td></tr><tr><td>`failureThreshold`</td><td>判定為**失敗**所需的連續檢查次數</td></tr></tbody></table>
        
        ---
        
        ### **小結：**
        
        
        - **`initialDelaySeconds`**：控制首次健康檢查的延遲時間。
        - **`periodSeconds`**：控制每次健康檢查的間隔時間。
        
        這些參數幫助 Kubernetes **有效管理容器的健康狀態**，從而避免過早或過頻的檢查導致誤判。
        
        如果需要進一步解釋，請告訴我！😊

---

#### Probe 支援的三種檢查方法：

1. **httpGet**
    
    
    - 發送 HTTP 請求，返回 200-400 範圍的狀態碼為成功。
2. **exec**
    
    
    - 執行 Shell 命令，返回狀態碼為 0 為成功。
3. **tcpSocket**
    
    
    - 發起 TCP Socket 建立成功。

---

### Kubernetes Pod 創建流程圖

#### **流程說明**：

1. **使用者** 向 **API Server** 發送 **create Pod** 的請求。
2. **API Server** 將 Pod 資訊 **寫入（write）** 到 **etcd**。
3. **etcd** 監控到 **新的 Pod** 被創建（`watch(new pod)`），通知 **Scheduler**。
4. **Scheduler** 將 Pod **綁定（bind pod）** 到節點。
5. **API Server** 再次將 **綁定資訊** 寫入 **etcd**。
6. **etcd** 監控到 **綁定的 Pod**（`watch(bound pod)`），通知 **Kubelet**。
7. **Kubelet** 監控到 **綁定的 Pod**，執行 **`docker run`** 指令來啟動容器。
8. **API Server** 更新 **Pod 狀態**，並將 **狀態更新資訊** 寫入 **etcd**。

[![image-1736756174112.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/yEhEffJcecK4G2dZ-image-1736756174112.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/yEhEffJcecK4G2dZ-image-1736756174112.png)

以下是圖片中的文字辨識及繁體中文翻譯：

---

### **8.Pod 調度**

#### 影響調用的屬性

#### 1. Pod 資源限制對 Pod 調用產生影響

```yaml
resources:
  requests:
    memory: "64Mi"
    cpu: "250m"

```

> 根據 `requests` 找到足夠資源的節點來進行 Pod 調度。

---

#### 2. 節點選擇器標籤對 Pod 調度的影響

```yaml
spec:
  nodeSelector:
    env_role: dev
containers:
  - name: nginx
    image: nginx:1.15

```

---

使用以下指令為節點打上標籤：

```bash
kubectl label node node1 env_role=prod

```

---

#### 3.節點親和性影響 Pod 調度

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: env_role
                operator: In
                values:
                  - dev
                  - test
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 1
          preference:
            matchExpressions:
              - key: group
                operator: In
                values:
                  - otherprod
containers:
  - name: webdemo
    image: nginx

```

---

##### **節點親和性（`nodeAffinity`）說明**

##### 節點親和性和 `nodeSelector` 基本一樣，根據節點上的標籤條件來決定 Pod 調度到哪些節點上。

1. **硬親和性**
    
    
    - 使用 `requiredDuringSchedulingIgnoredDuringExecution`
    - 條件必須滿足，否則無法調度到該節點。
2. **軟親和性**
    
    
    - 使用 `preferredDuringSchedulingIgnoredDuringExecution`
    - **嘗試滿足條件，但不保證**。如果滿足條件的節點可用，則優先調度到這些節點。

---

##### **重點區分**

- **硬親和性**：必須滿足條件，否則調度失敗。
- **軟親和性**：優先考慮符合條件的節點，但不強制。

---

# Controller 基本概念

## 1. **什麼是 Controller**

- 在集群上管理和運行容器的對象

---

## 2. **Pod 和 Controller 的關係**

- **Pod** 是通過 **Controller** 實現應用的運維，例如 **伸縮**、**滾動升級** 等
- **Pod 和 Controller** 之間通過 **label 標籤** 建立關係 (**selector**)

---

## 3. **Deployment 的應用場景**

- 部署 **無狀態應用**
- 管理 **Pod** 和 **ReplicaSet**
- 支援 **滾動升級**、**回滾**、**彈性伸縮** 等功能

#### **應用場景：**

- **Web 服務**
- **微服務**

---

以下是圖片中的文字辨識及繁體中文翻譯：

---

以下是圖片中的文字辨識及繁體中文翻譯：

---

### **4. 使用 Deployment 部署應用（YAML）**

```yaml
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: web

```

---

### **說明：**

- **`selector`**：用於選擇符合特定標籤（`matchLabels`）的 Pod。
- **`matchLabels`**：指定要匹配的標籤條件，此處為 `app: web`。
- **`labels`**：在模板中定義 Pod 的標籤，確保與 `selector` 中的標籤條件一致。

此 YAML 文件表示使用 Deployment 部署一個副本的應用，並將其標籤設置為 `app: web`，以便進行管理和選擇。

---

## 5. **應用升級回滾和彈性伸縮**

#### **應用升級**

```bash
[root@k8smaster ~]# kubectl set image deployment web nginx=nginx:1.15
deployment.apps/web image updated

```

---

#### **查看升級狀態**

```bash
[root@k8smaster ~]# kubectl rollout status deployment web
deployment "web" successfully rolled out

```

---

#### **查看升級版本**

```bash
[root@k8smaster ~]# kubectl rollout history deployment web
deployment.apps/web
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

```

---

#### **回滾到上一個版本**

```bash
[root@k8smaster ~]# kubectl rollout undo deployment web
deployment.apps/web rolled back
[root@k8smaster ~]# kubectl rollout status deployment web
Waiting for deployment "web" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "web" rollout to finish: 1 old replicas are pending termination...
deployment "web" successfully rolled out

```

---

#### **回滾到指定的版本**

```bash
[root@k8smaster ~]# kubectl rollout undo deployment web --to-revision=2
deployment.apps/web rolled back

```

```bash
[root@k8smaster ~]# kubectl rollout status deployment web
Waiting for deployment "web" rollout to finish: 1 out of 2 new replicas have been updated...
Waiting for deployment "web" rollout to finish: 1 old replicas are pending termination...
deployment "web" successfully rolled out

```

---

#### **說明：**

- 使用 `--to-revision=2` 指定要回滾到的版本號。
- **`kubectl rollout status`** 用於查看回滾的進度，確認部署是否成功完成。

以下是圖片中的文字辨識及繁體中文翻譯：

---

#### **彈性伸縮**

```bash
[root@k8smaster ~]# kubectl scale deployment web --replicas=10
deployment.apps/web scaled

```

---

#### **說明：**

- 使用 `kubectl scale` 指令對 Deployment 進行彈性伸縮。
- **`--replicas=10`** 表示將 Pod 副本數量調整為 10。

此操作用於根據業務需求動態調整應用的容器數量，實現資源的彈性管理。

# Service 基本概念

服務發現

[![image-1736821738265.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/gX9koeSWMn0HkT42-image-1736821738265.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/gX9koeSWMn0HkT42-image-1736821738265.png)

[![image-1736825475948.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/1OVr2BZBtK97bXM6-image-1736825475948.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/1OVr2BZBtK97bXM6-image-1736825475948.png)

以下是圖片內容的文字辨識及繁體中文翻譯：

---

## **1. 無狀態和有狀態**

### **(1) 無狀態：**

- 認為 **Pod** 都是一樣的
- 沒有順序要求
- 不用考慮在哪個 **Node** 運行
- 可以隨意進行伸縮和擴展

### **(2) 有狀態：**

- 上述因素都需要考慮到
- 讓每個 **Pod** 獨立，保持 **Pod** 啟動順序和唯一性
- 唯一的網路標識符，持久存儲
- 有順序，例如 **MySQL 主從**

---

## **2. 部署有狀態應用**

### **(1) 無頭 Service**

- **ClusterIP: None**

```yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
    - port: 80
      name: web
  clusterIP: None
  selector:
    app: nginx

```

---

### **(2) StatefulSet 部署有狀態應用**

```yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx-statefulset
  namespace: default

```

**指令：**

```bash
[root@k8smaster ~]# vi sts.yaml
[root@k8smaster ~]# kubectl apply -f sts.yaml
service/nginx created
statefulset.apps/nginx-statefulset created

```

---

### **說明：**

- **無頭 Service**：`ClusterIP: None` 用於有狀態應用，允許 Pod 之間直接通訊。
- **StatefulSet**：適用於有狀態應用，確保每個 Pod 都有唯一標識，並保證啟動順序。

---

以下是圖片內容的文字辨識及繁體中文翻譯：

---

### **查看 Pod**

- 有三個 Pod，每個都有唯一名稱：

```bash
[root@k8smaster ~]# kubectl get pods
NAME                    READY   STATUS    RESTARTS   AGE
nginx-statefulset-0     1/1     Running   0          74s
nginx-statefulset-1     1/1     Running   0          40s
nginx-statefulset-2     1/1     Running   0          21s

```

---

### **Deployment 和 StatefulSet 的區別**

- StatefulSet 有身份的（唯一標識的）。
- 根據主機名 + 按照一定規則生成域名。

---

### **查看創建的無頭 Service**

```bash
NAME      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1     <none>        443/TCP        6d17h
nginx        ClusterIP   None          <none>        80/TCP         2m12s
web          NodePort    10.96.18.7    <none>        80:31819/TCP   50m

```

- **每個 Pod 有唯一主機名**
- **唯一域名**

### **格式：**

```
主機名稱.service名稱.命名空間.svc.cluster.local

```

### **示例：**

```
nginx-statefulset-0.nginx.default.svc.cluster.local

```

---

### **說明：**

- StatefulSet 通過主機名和域名，確保每個 Pod 都有唯一標識並支持有狀態應用。
- 無頭 Service 的 ClusterIP 為 `None`，允許 Pod 之間直接通信。

---

以下是圖片內容的文字辨識及繁體中文翻譯：

---

## **3. 部署守護進程 DaemonSet**

- **在每個 Node 上運行一個 Pod，新加入的 Node 也同樣運行在一個 Pod 裡面。**

### **示例：**

在每個 Node 節點安裝數據採集工具。

---

### **指令操作：**

1. **部署 DaemonSet：**

```bash
[root@k8smaster ~]# kubectl apply -f ds.yaml
daemonset.apps/ds-test created

```

2. **查看 Pod：**

```bash
[root@k8smaster ~]# kubectl get pods
NAME                 READY   STATUS             RESTARTS   AGE
ds-test-cbk6v        0/1     ContainerCreating  0          0s
ds-test-cx6fk        1/1     Running            0          30s

```

3. **進入 Pod：**

```bash
[root@k8smaster ~]# kubectl exec -it ds-test-cbk6v bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in future versions.

```

4. **查看文件：**

```bash
root@ds-test-cx6fk:/# ls /tmp/log
anaconda   boot.log-20200902   containers   firewalld

```

---

### **說明：**

- **DaemonSet** 用於確保每個 Node 上都執行一個 Pod，例如安裝系統監控、日誌收集、數據採集等工具。
- **特點：** 新加入的 Node 會自動部署相應的 Pod，適合全局性的守護進程需求。

# ConfigMap

以下是圖片內容的辨識及繁體中文翻譯，並補充完整 **1-4 步驟** 的詳細說明：

---

### **ConfigMap 操作步驟**

#### **1. 創建配置文件**

創建一個配置文件，例如 `redis.properties`：

```bash
[root@k8smaster ~]# vi redis.properties
# 文件內容
redis.host=127.0.0.1
redis.port=6379
redis.password=123456

```

---

#### **2. 創建 ConfigMap**

將配置文件創建為 ConfigMap：

```bash
[root@k8smaster ~]# kubectl create configmap redis-config --from-file=redis.properties
configmap/redis-config created

[root@k8smaster ~]# kubectl get cm
NAME           DATA   AGE
redis-config   1      10s

```

檢查 ConfigMap 詳情：

```bash
[root@k8smaster ~]# kubectl describe cm redis-config
Name: redis-config
Data:
redis.properties:
  redis.host=127.0.0.1
  redis.port=6379
  redis.password=123456

```

---

#### **3. 以 Volume 形式挂載到 Pod 容器中**

**步驟：**

1. 編寫 YAML 文件，將 ConfigMap 作為 Volume 注入到 Pod 中：

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mycontainer
    image: busybox
    command: ["sh", "-c", "cat /config/redis.properties; sleep 3600"]
    volumeMounts:
    - name: config-volume
      mountPath: /config
  volumes:
  - name: config-volume
    configMap:
      name: redis-config

```

2. 應用配置：

```bash
[root@k8smaster ~]# kubectl apply -f cm.yaml
pod/mypod created

```

3. 查看 Pod 狀態並檢查配置：

```bash
[root@k8smaster ~]# kubectl get pods
NAME        READY   STATUS      RESTARTS   AGE
mypod       1/1     Completed   0          41s

[root@k8smaster ~]# kubectl logs mypod
redis.host=127.0.0.1
redis.port=6379
redis.password=123456

```

---

#### **4. 以變量形式挂載到 Pod 容器中**

**步驟：**

1. 編寫 YAML 文件，將 ConfigMap 值作為環境變量注入：

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mycontainer
    image: busybox
    command: ["sh", "-c", "echo $REDIS_HOST; echo $REDIS_PORT; echo $REDIS_PASSWORD; sleep 3600"]
    env:
    - name: REDIS_HOST
      valueFrom:
        configMapKeyRef:
          name: redis-config
          key: redis.host
    - name: REDIS_PORT
      valueFrom:
        configMapKeyRef:
          name: redis-config
          key: redis.port
    - name: REDIS_PASSWORD
      valueFrom:
        configMapKeyRef:
          name: redis-config
          key: redis.password

```

2. 應用配置：

```bash
[root@k8smaster ~]# kubectl apply -f myconfig.yaml
pod/mypod created

```

3. 查看 Pod 狀態並檢查日誌：

```bash
[root@k8smaster ~]# kubectl logs mypod
127.0.0.1
6379
123456

```

---

### **小結**

- **Volume 挂載**：將 ConfigMap 文件直接映射到容器的文件系統中。
- **環境變量注入**：將 ConfigMap 的鍵值對作為環境變量傳遞給容器。

# K8s 安全機制

以下是圖片的文字辨識及繁體中文翻譯：

---

### **Kubernetes 集群安全機制**

[![image-1737341555160.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/9Alz4LLG6GVwGnsM-image-1737341555160.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/9Alz4LLG6GVwGnsM-image-1737341555160.png)

#### **1. 概述**

1. **訪問 k8s 集群時**，需要經過三個步驟完成具體操作：
    
    
    - 第一步：認證
    - 第二步：授權
    - 第三步：準入控制
2. **訪問過程：**
    
    
    - 訪問時需要經過 `apiserver`，`apiserver` 做統一認證和驗證，例如門衛。
    - 訪問過程中需要 **證書**、**token**，或者用 **用戶名+密碼**。
    - 如果訪問 Pod，還需要 **serviceAccount**。

---

#### **第一步 認證：傳輸安全**

- **傳輸安全：**
    
    
    - 對外不暴露 8080 端口，只能內部訪問，對外使用端口 6443。
- **認證方式：**
    
    
    - `https` 證書認證：基於 CA 證書。
    - `http` token 認證：通過 token 識別用戶。
    - `http` 基本認證：使用用戶名+密碼認證。

---

#### **第二步 授權：**

- 基於 RBAC 進行授權操作。
- 基於角色訪問控制。

---

#### **第三步 準入控制：**

- 準入控制器的列表，根據列表決定是否允許執行操作。

---

### **2. RBAC**

### **基於角色的訪問控制**

- **角色：**
    
    
    - **role**：特定命名空間訪問權限
    - **ClusterRole**：所有命名空間訪問權限
- **角色綁定：**
    
    
    - **roleBinding**：角色綁定到主體
    - **ClusterRoleBinding**：集群角色綁定到主體

---

### **主體：**

- **user**：用戶
- **group**：用戶組
- **serviceAccount**：服務帳號

---

這是基於 RBAC 的角色和權限管理，適用於 Kubernetes 的訪問控制。如需進一步說明或具體範例，請隨時告訴我！😊

[![image-1737341811213.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/DdkVrwv0FBVX6fQ6-image-1737341811213.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/DdkVrwv0FBVX6fQ6-image-1737341811213.png)

以下是圖片中的文字辨識及翻譯成繁體中文：

---

### **1. 創建命名空間**

```bash
[root@m1 lucy]# kubectl create ns roledemo
namespace/roledemo created

```

---

### **2. 在新創建的命名空間中創建 Pod**

```bash
[root@m1]# kubectl run nginx --image=nginx -n roledemo

```

---

### **3. 創建角色**

```bash
[root@m1]# vi rbac-role.yaml
[root@m1]# kubectl apply -f rbac-role.yaml
role.rbac.authorization.k8s.io/pod-reader created

[root@m1]# kubectl get role -n roledemo
NAME         AGE
pod-reader   19s

```

---

### **4. 創建角色綁定**

```bash
[root@m1]# vi rbac-rolebinding.yaml
[root@m1]# kubectl apply -f rbac-rolebinding.yaml
rolebinding.rbac.authorization.k8s.io/read-pods created

[root@m1]# kubectl get rolebinding -n roledemo
NAME          AGE
read-pods     15s

```

---

### **5. 使用證書識別身份**

```bash
[root@m1 mary]# vi rbac-user.sh
[root@m1 mary]# cp /root/TLS/k8s/ca* ./
[root@m1 mary]# bash rbac-user.sh
2020/09/03 17:03:10 [INFO] generate received request
2020/09/03 17:03:10 [INFO] received CSR

[root@m1 mary]# kubectl get pods -n roledemo

```

---

### **說明：**

1. **命名空間** 用於區分不同的資源和工作負載。
2. **角色和角色綁定** 控制對特定命名空間的訪問權限。
3. **使用證書識別身份** 是基於 RBAC 的身份驗證機制，確保用戶能正確訪問資源。

# Ingress 概述

[![image-1737343343204.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/k8k7s6z3OEy6PZa7-image-1737343343204.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/k8k7s6z3OEy6PZa7-image-1737343343204.png)

---

## **Ingress**

### **1. 將端口號對外暴露，通過 IP + 端口號進行訪問**

- **使用 Service 裡面的 NodePort 實現**

---

### **2. NodePort 缺陷**

- **在每個節點上都會起到端口**，在訪問時可以通過任何節點，通過節點 IP + 暴露端口號實現訪問。
- 意味著每個端口只能使用一次，一個端口對應一個應用。
- **實際訪問中都是用域名**，根據不同域名跳轉到不同端口服務中。

---

### **3. Ingress 和 Pod 的關係d**

- **Pod 和 Ingress 是通過 Service 關聯的**。
- **Ingress 作為統一入口**，由 Service 關聯一組 Pod。

---

### **說明：**

- **Ingress** 提供基於域名的訪問路由能力，實現集群內外通信的靈活控制。
- 它比 NodePort 更靈活且節約端口資源，是 Kubernetes 中常用的負載均衡工具之一。

以下是圖片內容的文字辨識及翻譯成繁體中文：

---

## **5. 使用 Ingress**

- **第一步：** 部署 Ingress Controller
- **第二步：** 創建 Ingress 規則

---

我們在這裡選擇官方維護的 NGINX 控制器，實現部署。

---

以下是圖片中的文字辨識及翻譯成繁體中文：

---

## **6. 使用 Ingress 對外暴露應用**

### **(1) 創建 Nginx 應用，對外暴露端口使用 NodePort**

```bash
kubectl create deployment web --image=nginx

kubectl expose deployment web --port=80 --target-port=80 --type=NodePort

```

---

### **(2) 部署 Ingress Controller**

```bash
[root@k8smaster ~]# vi ingress-con.yaml
[root@k8smaster ~]# kubectl apply -f ingress-con.yaml
namespace/nginx-ingress created
configmap/nginx-configuration created
configmap/tcp-services created
configmap/udp-services created
serviceaccount/nginx-ingress-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created
role.rbac.authorization.k8s.io/nginx-ingress-role created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created

```

---

### **說明：**

1. **NodePort 暴露應用：**
    - 通過 `NodePort` 將應用對外暴露，允許外部訪問特定端口。
2. **Ingress Controller 部署：**
    - 使用配置文件 `ingress-con.yaml` 部署 Ingress 控制器，啟用基於域名的路由功能。

# helm 概述

[![image-1737350287413.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/t6qsyMHUPkTrcAXu-image-1737350287413.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/t6qsyMHUPkTrcAXu-image-1737350287413.png)

### **Helm**

#### **1. Helm 引入**

1. **之前方式部署應用的基本過程：**
    
    
    - 編寫 YAML 文件： 
        - Deployment
        - Service
        - Ingress
2. **如果使用之前方式部署單一應用，少數服務的應用，比較合適：**
    
    
    - 例如部署微服務項目，可能有幾十個服務，每個服務都有一套 YAML 文件，需要維護大量 YAML 文件，版本管理特別不方便。

---

#### **2. 使用 Helm 可以解決哪些問題？**

1. 使用 Helm 可以把這些 YAML 作為一個整體管理。
2. 實現 YAML 高效複用。
3. 使用 Helm 應用級別的版本管理。

---

#### **3. Helm 介紹**

Helm 是一個 Kubernetes 的包管理工具，類似於 Linux 下的包管理器，如 yum/apt 等，可以很方便地將之前打包好的 YAML 文件部署到 Kubernetes 上。

---

#### **4.Helm 有 3 個重要概念：**

1. **Helm**：  
    一個命令行客戶端工具，主要用於 Kubernetes 應用 Chart 的創建、打包、發布和管理。
2. **Chart**：  
    應用描述，一系列用於描述 Kubernetes 資源相關文件的集合。
3. **Release**：  
    基於 Chart 的部署實體。一個 Chart 被 Helm 運行後將會生成對應的一個 Release；  
    它會在 Kubernetes 中創建出真實運行的資源對象。

---

#### **5.Helm 在 2019 年發布 V3 版本，和之前版本相比有變化**

1. **V3 版本刪除 Tiller**，架構變化。
2. **Release** 可以在不同命名空間重用。
3. **將 Chart 推送到 Docker 倉庫中**。

---

以下是圖片內容的文字辨識及翻譯成繁體中文：

---

### **1. Helm 安裝**

1. **下載 Helm 安裝壓縮文件，上傳到 Linux 系統中**
    
    ```bash
    [root@k8smaster ~]# tar zxvf helm-v3.0.0-linux-amd64.tar.gz
    linux-amd64/helm
    linux-amd64/README.md
    linux-amd64/LICENSE
    
    ```
2. **解壓 Helm 壓縮文件，將解壓後的 Helm 目錄複製到 `/usr/bin` 目錄下**
    
    ```bash
    [root@k8smaster linux-amd64]# mv helm /usr/bin
    [root@k8smaster linux-amd64]# helm
    
    ```

---

### **2. 配置 Helm 倉庫**

#### **(1) 添加倉庫**

```bash
helm repo add 倉庫名稱 倉庫地址

```

示例：

```bash
[root@k8smaster linux-amd64]# helm repo add stable http://mirror.azure.cn/kubernetes/charts
"stable" has been added to your repositories

```

檢查倉庫列表：

```bash
[root@k8smaster linux-amd64]# helm repo list
NAME    URL
stable  http://mirror.azure.cn/kubernetes/charts

```

---

#### **(2) 更新倉庫地址**

```bash
[root@k8smaster linux-amd64]# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "aliyun" chart repository
...Successfully got an update from the "stable" chart repository
Update Complete. Happy Helming!

```

---

### **(3) 刪除倉庫**

```bash
[root@k8smaster linux-amd64]# helm repo remove aliyun
"aliyun" has been removed from your repositories

```

---

### **說明：**

- 使用 `helm repo remove <倉庫名稱>` 可以刪除已添加的 Helm 倉庫。
- 這樣可以清理不再需要的倉庫條目，保持倉庫列表整潔。

---

以下是圖片內容的文字辨識及翻譯成繁體中文：

---

```bash
[root@k8smaster linux-amd64]# helm search repo weave
NAME                 CHART VERSION   APP VERSION    DESCRIPTION
stable/weave-cloud   0.3.7           1.4.0          Weave Cloud is a add-on...
stable/weave-scope   1.1.10          1.12.0         A Helm chart for the We...

[root@k8smaster linux-amd64]# helm install ui stable/weave-scope
NAME: ui
LAST DEPLOYED: Mon Sep 7 14:22:47 2020
NAMESPACE: default
STATUS: deployed

[root@k8smaster linux-amd64]# helm list
NAME    NAMESPACE   REVISION    UPDATED                                 STATUS
ui      default     1           2020-09-07 14:22:47.383427149 +0800 CST deployed

[root@k8smaster linux-amd64]# helm status ui
NAME: ui
LAST DEPLOYED: Mon Sep 7 14:22:47 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1

```

---

1. **搜索 Helm 倉庫中的 Weave 應用**
    
    
    - 使用命令 `helm search repo weave`，顯示兩個可用的應用： 
        - **stable/weave-cloud**：版本 0.3.7，應用版本 1.4.0。
        - **stable/weave-scope**：版本 1.1.10，應用版本 1.12.0。
2. **安裝 Weave Scope**
    
    
    - 使用命令 `helm install ui stable/weave-scope` 將 Weave Scope 安裝到 Kubernetes 中，並命名為 **ui**。
3. **查看已安裝的 Helm 應用列表**
    
    
    - 使用命令 `helm list`，顯示應用 **ui** 已成功部署，位於命名空間 **default**。
4. **查看應用狀態**
    
    
    - 使用命令 `helm status ui`，顯示應用名稱 **ui**，狀態為 **deployed**，部署版本為 **1**。

---

以下是圖片內容的文字辨識及翻譯成繁體中文：

---

### **如何自己創建 Chart**

#### **1. 使用命令創建 Chart**

```bash
helm create chart名稱

```

示例：

```bash
[root@k8smaster ~]# helm create mychart
Creating mychart
[root@k8smaster mychart]# ls
charts  Chart.yaml  templates  values.yaml

```

- **Chart.yaml**：當前 Chart 屬性配置信息。
- **templates**：編寫 YAML 文件放到這個目錄中。
- **values.yaml**：YAML 文件可以使用全局變量。

---

#### **2. 在 templates 文件夾創建兩個 YAML 文件**

- **deployment.yaml**
- **service.yaml**

示例：

```bash
[root@k8smaster templates]# ls
deployment.yaml  service.yaml

```

---

### **說明：**

1. **Helm create** 命令自動生成 Chart 的目錄結構和必要文件，方便進一步編輯。
2. **templates 文件夾** 是放置 Kubernetes 配置文件（如 Deployment、Service 等 YAML 文件）的地方。
3. **values.yaml** 文件允許定義變量，提供動態和靈活的配置支持。

---

以下是圖片內容的文字辨識及翻譯成繁體中文：

---

### **3. 安裝 mychart**

```bash
[root@k8smaster ~]# helm install web1 mychart/
NAME: web1
LAST DEPLOYED: Mon Sep 7 15:03:17 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

```

---

### **說明：**

1. **命令解釋**：
    
    
    - `helm install`：用於安裝 Chart。
    - `web1`：為此次安裝命名為 `web1`。
    - `mychart/`：指定需要安裝的 Chart 目錄。
2. **輸出信息**：
    
    
    - **NAME**：安裝的應用名稱，這裡是 `web1`。
    - **LAST DEPLOYED**：最後部署的時間。
    - **NAMESPACE**：部署的命名空間，默認為 `default`。
    - **STATUS**：應用部署狀態，這裡是 `deployed`（已部署）。
    - **REVISION**：部署的版本號，這裡是 `1`。
    - **TEST SUITE**：測試套件信息，這裡是 `None`（沒有測試）。

---

### **4. 應用升級**

```bash
helm upgrade chart名稱

```

示例：

```bash
[root@k8smaster ~]# helm upgrade web1 mychart/
Release "web1" has been upgraded. Happy Helming!

```

---

### **說明：**

1. **命令解釋**：
    
    
    - `helm upgrade`：用於升級已部署的 Chart 應用。
    - `web1`：需要升級的應用名稱。
    - `mychart/`：指定升級所基於的 Chart 目錄。
2. **輸出信息**：
    
    
    - **Release "web1" has been upgraded**：表示應用 `web1` 已成功升級。

---

以下是圖片內容的文字辨識及翻譯成繁體中文：

---

### **實現 YAML 高效複用**

- **通過傳遞參數，動態渲染模板，YAML 內容動態傳入參數生成。**

```bash
[root@k8smaster mychart]# ls
charts  Chart.yaml  templates  values.yaml

```

- **在 Chart 中有 `values.yaml` 文件，定義 YAML 文件全局變量。**

---

### **操作步驟：**

1. **在 `values.yaml` 定義變量和值。**
2. **在具體 YAML 文件中，獲取定義變量值。**

---

### **說明：**

- YAML 文件大體有幾個地方不同的參數： 
    - **image**
    - **tag**
    - **label**
    - **port**
    - **replicas**

---

### **說明：**

使用 `values.yaml` 提高了 Chart 的靈活性，可以輕鬆實現模板的複用和參數化，方便不同環境配置。

---

# k8s 持久化儲存

以下是圖片內容的文字辨識及翻譯成繁體中文：

---

### **數據卷 emptydir，是本地存儲，pod 重啟，數據不存在了，需要對數據持久化存儲**

#### **1. NFS，網絡存儲**

- **pod 重啟，數據還存在的方式**

---

### **第一步：找一台服務器作為 NFS 服務端**

#### **(1) 安裝 NFS**

```bash
yum install -y nfs-utils

```

---

#### **(2) 設置掛載路徑**

```bash
[root@atonline ntest /]# vi /etc/exports
/data/nfs *(rw,no_root_squash)

```

---

#### **(3) 掛載路徑需要創建出來**

```bash
[root@atonline ntest data]# mkdir nfs
[root@atonline ntest data]# ls
nfs

```

---

### **說明：**

1. **emptydir** 是 Kubernetes 中的一種臨時存儲方式，pod 重啟後數據會丟失。
2. **NFS（網絡文件系統）** 提供一種數據持久化存儲解決方案，pod 重啟後數據仍然保留。
3. **操作流程**： 
    - 安裝 NFS 軟件。
    - 配置 NFS 的掛載路徑。
    - 確保掛載的目錄已創建。

---

以下是圖片內容的文字辨識及翻譯成繁體中文：

---

### **第二步：在 k8s 集群 node 節點安裝 NFS**

```bash
yum install -y nfs-utils

```

---

### **說明：**

- 在 Kubernetes 集群的每個節點上安裝 **NFS 客戶端工具** (`nfs-utils`)，以便節點能夠掛載 NFS 共享存儲。

---

以下是圖片內容的文字辨識及翻譯成繁體中文：

---

### **第三步：在 NFS 服務器啟動 NFS 服務**

```bash
# systemctl start nfs
# ps -ef | grep nfs
15:03 ? 00:00:00 [nfsd4_callbacks]

```

---

### **第四步：在 k8s 集群部署應用使用 NFS 持久化網絡存儲**

```bash
vi nfs-nginx.yaml
kubectl apply -f nfs-nginx.yaml

[root@k8smaster pv]# kubectl exec -it nginx-depl-6b96bc8d7d-92qhv bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version.

root@nginx-depl-6b96bc8d7d-92qhv:/# ls /usr/share/nginx/html
index.html

```

---

### **說明：**

#### **第三步：啟動 NFS 服務**

1. **啟動 NFS 服務**：通過 `systemctl start nfs`。
2. **驗證 NFS 啟動狀態**：使用 `ps -ef | grep nfs` 查看進程是否正常啟動。

#### **第四步：部署應用並使用 NFS**

1. **編輯 YAML 文件**：創建或修改 `nfs-nginx.yaml` 文件，配置 NFS 為持久化存儲。
2. **應用配置文件**：使用 `kubectl apply` 部署應用。
3. **驗證 NFS 存儲是否生效**： 
    - 使用 `kubectl exec` 進入 Pod。
    - 確認存儲目錄（如 `/usr/share/nginx/html`）內的文件是否存在，例如 `index.html`。

---

### **PV 和 PVC**

1. **PV（Persistent Volume，持久化存儲）：**
    
    
    - 持久化存儲，對存儲資源進行抽象。
    - 對外提供可調用的地方（生產者）。
2. **PVC（Persistent Volume Claim，持久化存儲請求）：**
    
    
    - 用於調用，不需要關心內部實現細節（消費者）。

[![image-1737425386000.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/osWVdYXoaoWBvta2-image-1737425386000.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/osWVdYXoaoWBvta2-image-1737425386000.png)

# k8s 監控

### **集群資源監控**

### **1. 監控指標**

- **集群監控**
    
    
    - 節點資源利用率
    - 節點數量
    - 運行的 Pods
- **Pod 監控**
    
    
    - 容器指標
    - 應用程序

---

### **2. 監控平台搭建方案：Prometheus + Grafana**

#### **(1) Prometheus**

- 開源的
- 具備監控、報警、數據庫功能
- 以 HTTP 協議定期抓取被監控組件的狀態
- 不需要複雜的集成過程，使用 HTTP 接口接入即可

---

#### **(2) Grafana**

- 開源的數據分析和可視化工具
- 支援多種數據源

[![image-1737431399886.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/k2Kgei9VrKUtm41G-image-1737431399886.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/k2Kgei9VrKUtm41G-image-1737431399886.png)

```YAML
# rbac-setup.yaml
apiGroups: ["*"]
resources:
  - nodes
  - nodes/proxy
  - services
  - endpoints
  - pods
verbs: ["get", "list", "watch"]
---
apiGroups:
  - extensions
resources:
  - ingresses
verbs: ["get", "list", "watch"]
nonResourceURLs: ["/metrics"]
verbs: ["get"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: prometheus
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: prometheus
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: prometheus
subjects:
  - kind: ServiceAccount
    name: prometheus
    namespace: kube-system
```

# k8s 高可用集群

[![image-1737445662276.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/IWsIsOcoqEizYTKK-image-1737445662276.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/IWsIsOcoqEizYTKK-image-1737445662276.png)

[![image-1737445931517.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/g7khJjvP9vaPZrx8-image-1737445931517.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/g7khJjvP9vaPZrx8-image-1737445931517.png)

[![image-1737447248701.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/FqBt277RZDlRMTfT-image-1737447248701.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/FqBt277RZDlRMTfT-image-1737447248701.png)

# k8s 部署流程

[![image-1737453741721.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/pIMK0hBwUkVbAUvB-image-1737453741721.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/pIMK0hBwUkVbAUvB-image-1737453741721.png)

[![image-1737454023904.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/E7Z2huIbJXOtAhJY-image-1737454023904.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/E7Z2huIbJXOtAhJY-image-1737454023904.png)

```
FROM ubuntu:22.04

# 設定時區為台北
ENV TZ=Asia/Taipei
RUN apt-get update && apt-get install -y tzdata && \
    ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && \
    echo $TZ > /etc/timezone && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# 複製並執行安裝腳本
COPY install.sh /tmp/install.sh
RUN chmod +x /tmp/install.sh && /tmp/install.sh

```

<div drawio-diagram="1405"><img src="https://bookstack.treemanou.com/uploads/images/drawio/2025-01/7jPAwVPACj2CBvhc-drawing-3-1737454262.png" alt=""/></div>

# k8s核心組件

[![image-1737708797625.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/VHfefOFyJ2w3ujxH-image-1737708797625.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/VHfefOFyJ2w3ujxH-image-1737708797625.png)

[![image-1775714710761.png](https://bookstack.treemanou.com/uploads/images/gallery/2026-04/scaled-1680-/wGSVTF0PqNV9DXob-image-1775714710761.png)](https://bookstack.treemanou.com/uploads/images/gallery/2026-04/wGSVTF0PqNV9DXob-image-1775714710761.png)

**3. k8s 集群架構組件**

- **Master**（主控節點）和 **Node**（工作節點）

**(1) Master 組件**

- **apiserver**  
    集群統一入口，以 RESTful 方式，交給 etcd 存儲
- **scheduler**  
    節點調度，選擇 Node 節點應用部署
- **controller-manager**  
    處理集群中常規的後台任務，一個資源對應一個控制器
- **etcd**  
    存儲系統，用於保存集群相關的數據

**(2) Node 組件**

- **kubelet**
    
    
    - Master 排到 Node 節點代表，管理本機容器。
- **kube-proxy**
    
    
    - 提供網絡代理，負載均衡等操作。

[![image-1737710111332.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/9yvSvsn0UIJSvrjb-image-1737710111332.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/9yvSvsn0UIJSvrjb-image-1737710111332.png)

[![image-1737712398081.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/xmcWqhFoJCkTOuXa-image-1737712398081.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/xmcWqhFoJCkTOuXa-image-1737712398081.png)

---

**K8S 核心概念**

1. **Pod**
    
    
    - 最小部署單元
    - 一組容器的集合
    - 共享網絡
    - 生命週期是短暫的
2. **Controller**
    
    
    - 確保預期的 Pod 副本數量
    - 無狀態應用部署
    - 有狀態應用部署
    - 確保所有的 Node 運行同一個 Pod
    - 一次性任務和定時任務
3. **Service**
    
    
    - 定義一組 Pod 的訪問規則

---

如有其他需求，請隨時告訴我！

[![image-1737708872912.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/scaled-1680-/nPoPBQnry7LR1o2F-image-1737708872912.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-01/nPoPBQnry7LR1o2F-image-1737708872912.png)

kubectl run podtest --image=nginx

  
1\. kubectl向apiserver發送一個創建pod的請求，apiserver會將數據放到etcd存儲。

2\. Scheduler通過list-watch的方式，收到未綁定pod資源，通過自身調度算法選擇一個合適的node進行綁定，然後響應給apiserver，把信息更新到etcd中。

3\. Kubelet同樣通過list-watch的方式，收到分配到自己節點上pod，調用docker api創建容器，然後將容器狀態響應給apiserver，同時，把容器的信息、事件及狀態也通過apiserver寫入到etcd中。

# 2026 k8s 需知

從 5 年前（2021年）的 K8s 基礎出發，系統性地梳理了 **2026 年現代化 Kubernetes 架構**的演進。

身為系統建設負責人與架構師，以下是整理的核心重點：

### 1. 基礎架構的根本變革

<div aria-busy="false" aria-live="off" id="bkmrk-%E8%84%AB%E9%9B%A2-docker%EF%BC%9A-k8s-%E5%B7%B2%E7%A7%BB%E9%99%A4-d"><div aria-busy="false" aria-live="off">- **脫離 Docker：** K8s 已移除 Dockershim，底層運行環境轉為 **containerd/CRI-O**。
- **輕量化首選：** **K3s** (K8s/2) 成為主流，適合邊緣運算與本地測試。
- **工具鏈升級：** 建議在 Mac 使用 **OrbStack** 快速啟動 K3s；使用 **Kind** 模擬多節點生產環境。
- **遷移利器：** 使用 **Kompose** 將舊有的 `docker-compose.yaml` 轉換為 K8s 物件。

</div></div>### 2. 網路與流量調度 (最核心的演進)

<div aria-busy="false" aria-live="off" id="bkmrk-gateway-api-%E5%8F%96%E4%BB%A3-ingre"><div aria-busy="false" aria-live="off">- **Gateway API 取代 Ingress：** 這是 2026 年最重要的標準。透過角色分離（GatewayClass, Gateway, HTTPRoute）解決權限與配置混亂問題。
- **Service 角色重新定義：**
    
    
    - **ClusterIP：** 內部通訊基石，實現 Pod 間的備援與負載平衡。
    - **NodePort：** 您的實體環境中，對接手動 SLB 的關鍵入口。
    - **LoadBalancer：** 雲端自動化專用，在地端手動環境可被 NodePort 取代。
    - **ExternalName：** 用於將叢集外部服務（如舊資料庫）內部化。
- **現代交警 Traefik：** K3s 內建，支援自動發現與 Gateway API，負責精準指揮 Traffic。

</div></div>### 3. 高階治理與維運

<div aria-busy="false" aria-live="off" id="bkmrk-%E7%89%88%E6%9C%AC%E8%88%87%E6%AC%8A%E9%99%90%E6%8E%A7%E5%88%B6%EF%BC%9A-%E9%80%8F%E9%81%8E-label-%28v"><div aria-busy="false" aria-live="off">- **版本與權限控制：** 透過 **Label (v1/v2)** 區分版本，並利用 **HTTPRoute Weight** 實現金絲雀發佈或藍綠部署。
- **Service Mesh (服務網格)：** 如 Istio 或 Cilium，處理東西向流量的加密 (mTLS)、觀測與斷路保護。
- **YAML 物件現代化：** 建議在既有清單中加入 **StorageClass** (動態儲存)、**NetworkPolicy** (零信任安全) 與 **ServiceAccount** (權限管理)。

---

</div></div>### 給架構師的實務建議路徑：

<div aria-busy="false" aria-live="off" id="bkmrk-%E6%9C%AC%E5%9C%B0%E5%AF%A6%E9%A9%97%EF%BC%9A-%E5%9C%A8-orbstack-%E5%95%9F%E5%8B%95-"><div aria-busy="false" aria-live="off">1. **本地實驗：** 在 **OrbStack** 啟動 K8s，將一個簡單服務從 `docker-compose` 透過 `kompose` 搬上去。
2. **網路重構：** 練習不寫 Ingress，直接用 **Gateway API + HTTPRoute** 透過 NodePort 對接您的實體 SLB。
3. **穩定性強化：** 試著部屬兩個版本的 Pod，並在 `HTTPRoute` 調整權重進行流量切分。

</div></div>---

  
在 2026 年的本地開發環境中，**Minikube** 已不再是唯一或首選的工具。對於像您這樣追求效能、且在 Mac (Apple Silicon) 環境下工作的架構師來說，目前的替代方案主要分為兩類：**極簡整合型**與**專業容器型**。

以下是目前最推薦的替代方案：

<div aria-busy="false" aria-live="off" id="bkmrk--0"><div aria-busy="false" aria-live="off">---

</div></div>### 1. OrbStack (Mac 用戶的頂級首選)

如果您在 Mac 上工作，**OrbStack** 是目前公認最強大的 Minikube 替代品。它不是一個獨立的 K8s 工具，而是一個極輕量化的 Docker Desktop 替代方案，內建了 Kubernetes 支持。

<div aria-busy="false" aria-live="off" id="bkmrk-%E6%8A%80%E8%A1%93%E6%A0%B8%E5%BF%83%EF%BC%9A-%E5%85%A7%E5%BB%BA%E5%9F%BA%E6%96%BC-k3s-%E7%9A%84%E8%BC%95%E9%87%8F%E5%8C%96%E5%8F%A2"><div aria-busy="false" aria-live="off">- **技術核心：** 內建基於 **K3s** 的輕量化叢集。
- **優勢：**
    
    
    - **極速啟動：** 啟動 K8s 僅需約 2-5 秒。
    - **低資源消耗：** 閒置時幾乎不佔 CPU，且 RAM 是動態分配的。
    - **原生網路：** 支援直接透過 `.orb.local` 網域存取叢集內的 Service，不需要手動 `port-forward`。
- **適用場景：** 日常開發、快速測試 API 與微服務。

---

</div></div>### 2. Kind (Kubernetes in Docker)

這是目前 DevOps 生態系中的標準工具，也是我們之前討論過、最適合模擬生產環境的工具。

<div aria-busy="false" aria-live="off" id="bkmrk-%E6%8A%80%E8%A1%93%E6%A0%B8%E5%BF%83%EF%BC%9A-%E5%B0%87%E6%AF%8F%E5%80%8B-k8s-%E7%AF%80%E9%BB%9E%E8%B7%91%E5%9C%A8%E4%B8%80%E5%80%8B"><div aria-busy="false" aria-live="off">- **技術核心：** 將每個 K8s 節點跑在一個 Docker 容器內。
- **優勢：**
    
    
    - **模擬多節點：** 透過一個 YAML 配置就能啟動 1 個 Master + N 個 Worker 節點。
    - **CI/CD 友好：** 因為底層是 Docker，它可以在 GitLab Runner 或 GitHub Actions 中輕鬆執行。
    - **Gateway API 測試：** 非常適合用來測試最新的 Gateway API 或 Service Mesh 配置。
- **適用場景：** 測試集群架構行為（如 Pod 親和性、節點故障轉移）。

---

</div></div>### 3. K3d (K3s in Docker)

這是專門為 **K3s** 設計的封裝工具，類似於 Kind，但底層跑的是更輕量的 K3s。

<div aria-busy="false" aria-live="off" id="bkmrk-%E5%84%AA%E5%8B%A2%EF%BC%9A-%E7%B9%BC%E6%89%BF%E4%BA%86-k3s-%E7%9A%84%E5%BF%AB%E9%80%9F%E8%88%87%E4%BD%8E%E8%B3%87%E6%BA%90%E7%89%B9"><div aria-busy="false" aria-live="off">- **優勢：** 繼承了 K3s 的快速與低資源特性，同時擁有 Kind 的多節點管理能力。
- **適用場景：** 需要模擬多節點但電腦資源（RAM）較為吃緊時。

---

</div></div>### 4. 方案對照表

<div aria-busy="false" aria-live="off" id="bkmrk-%E5%B7%A5%E5%85%B7-%E5%95%9F%E5%8B%95%E9%80%9F%E5%BA%A6-%E8%B3%87%E6%BA%90%E4%BD%94%E7%94%A8-%E6%A8%A1%E6%93%AC%E5%A4%9A%E7%AF%80%E9%BB%9E-%E6%8E%A8"><div aria-busy="false" aria-live="off"><table data-path-to-node="16"><thead><tr><td>**工具**</td><td>**啟動速度**</td><td>**資源佔用**</td><td>**模擬多節點**</td><td>**推薦等級 (2026)**</td></tr></thead><tbody><tr><td><span data-path-to-node="16,1,0,0">**OrbStack**</span></td><td><span data-path-to-node="16,1,1,0">極快</span></td><td><span data-path-to-node="16,1,2,0">極低</span></td><td><span data-path-to-node="16,1,3,0">較難 (內建單節點)</span></td><td><span data-path-to-node="16,1,4,0">⭐⭐⭐⭐⭐ (Mac 首選)</span></td></tr><tr><td><span data-path-to-node="16,2,0,0">**Kind**</span></td><td><span data-path-to-node="16,2,1,0">中等</span></td><td><span data-path-to-node="16,2,2,0">中等</span></td><td><span data-path-to-node="16,2,3,0">**極易**</span></td><td><span data-path-to-node="16,2,4,0">⭐⭐⭐⭐ (架構測試首選)</span></td></tr><tr><td><span data-path-to-node="16,3,0,0">**K3d**</span></td><td><span data-path-to-node="16,3,1,0">快</span></td><td><span data-path-to-node="16,3,2,0">低</span></td><td><span data-path-to-node="16,3,3,0">容易</span></td><td><span data-path-to-node="16,3,4,0">⭐⭐⭐⭐ (輕量首選)</span></td></tr><tr><td><span data-path-to-node="16,4,0,0">**Minikube**</span></td><td><span data-path-to-node="16,4,1,0">慢</span></td><td><span data-path-to-node="16,4,2,0">高 (需 VM)</span></td><td><span data-path-to-node="16,4,3,0">普通</span></td><td><span data-path-to-node="16,4,4,0">⭐⭐ (已逐漸式微)</span></td></tr></tbody></table>

---

</div></div>### 專業建議：

如果您現在要建置本地環境：

<div aria-busy="false" aria-live="off" id="bkmrk-%E5%AE%89%E8%A3%9D-orbstack%EF%BC%9A-%E4%BD%9C%E7%82%BA%E6%82%A8%E7%9A%84-do"><div aria-busy="false" aria-live="off">1. **安裝 OrbStack：** 作為您的 Docker 與基礎 K8s 環境，解決 90% 的開發需求。
2. **搭配 Kind：** 當您需要測試複雜的負載平衡、多節點調度或跨節點網路時，在 OrbStack 上跑 Kind 叢集。

</div></div>

# Kubernetes Gateway API  測試

您說得對，為了確保這份文件是「自包含 (Self-contained)」且可立即執行的，必須包含後端應用的部署宣告。

身為架構師，在測試金絲雀發佈時，我們會使用具備 **不同環境變數** 的 Pod 來區分版本。以下是完整的 `apps.yaml` 內容與更新後的部署流程。

<div aria-busy="false" aria-live="polite" id="bkmrk-"><div aria-busy="false" aria-live="polite">---

</div></div>## 1. 應用程式定義層：`apps.yaml`

此檔案定義了兩個版本的 Deployment 與對應的 Service。我們使用 `nginxdemos/hello` 映像檔，因為它會直接在網頁內容中顯示 **Server Name**，方便我們觀察流量去向。

<div aria-busy="false" aria-live="polite" id="bkmrk-yaml-%23-----version-1"><div aria-busy="false" aria-live="polite"><div data-hveid="0" data-ved="0CAAQhtANahgKEwjssI2WoeCTAxUAAAAAHQAAAAAQ3wk"><div><span class="ng-tns-c1695782856-863">YAML</span><div><button aria-label="複製代碼" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger copy-button ng-tns-c1695782856-863 mat-unthemed ng-star-inserted"></button></div></div><div><div>```
<span class="hljs-comment"># --- Version 1 (90% 流量) ---</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">my-app-v1</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">2</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">my-app</span>
      <span class="hljs-attr">version:</span> <span class="hljs-string">v1</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">my-app</span>
        <span class="hljs-attr">version:</span> <span class="hljs-string">v1</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">nginx</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">nginxdemos/hello:plain-text</span>
        <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">80</span>
<span class="hljs-meta">---</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">my-app-v1</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">my-app</span>
    <span class="hljs-attr">version:</span> <span class="hljs-string">v1</span>
  <span class="hljs-attr">ports:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
    <span class="hljs-attr">port:</span> <span class="hljs-number">80</span>
    <span class="hljs-attr">targetPort:</span> <span class="hljs-number">80</span>
<span class="hljs-meta">---</span>
<span class="hljs-comment"># --- Version 2 (10% 權重測試) ---</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">my-app-v2</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">2</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">my-app</span>
      <span class="hljs-attr">version:</span> <span class="hljs-string">v2</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">my-app</span>
        <span class="hljs-attr">version:</span> <span class="hljs-string">v2</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">nginx</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">nginxdemos/hello:plain-text</span>
        <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">80</span>
<span class="hljs-meta">---</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">my-app-v2</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">my-app</span>
    <span class="hljs-attr">version:</span> <span class="hljs-string">v2</span>
  <span class="hljs-attr">ports:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
    <span class="hljs-attr">port:</span> <span class="hljs-number">80</span>
    <span class="hljs-attr">targetPort:</span> <span class="hljs-number">80</span>

```

</div></div></div>---

</div></div>## 2. 完整部署與測試標準 SOP (全流程)

### 第一階段：基礎資源與控制器

<div aria-busy="false" aria-live="polite" id="bkmrk-bash-%23-1.-%E5%AE%89%E8%A3%9D-gateway"><div aria-busy="false" aria-live="polite"><div data-hveid="0" data-ved="0CAAQhtANahgKEwjssI2WoeCTAxUAAAAAHQAAAAAQ4Ak"><div><span class="ng-tns-c1695782856-864">Bash</span><div><button aria-label="複製代碼" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger copy-button ng-tns-c1695782856-864 mat-unthemed ng-star-inserted"></button></div></div><div><div>```
<span class="hljs-comment"># 1. 安裝 Gateway API 與 Traefik CRDs</span>
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml
kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.0/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml

<span class="hljs-comment"># 2. 部署 Traefik (對齊 8000 埠)</span>
helm upgrade --install traefik traefik/traefik \
  --namespace traefik --create-namespace \
  --<span class="hljs-built_in">set</span> ports.web.nodePort=30080 \
  --<span class="hljs-built_in">set</span> ports.web.port=8000 \
  --<span class="hljs-built_in">set</span> service.type=NodePort \
  --<span class="hljs-built_in">set</span> providers.kubernetesGateway.enabled=<span class="hljs-literal">true</span> \
  --<span class="hljs-built_in">set</span> additionalArguments=<span class="hljs-string">"{--providers.kubernetesgateway,--entrypoints.web.address=:8000}"</span> \
  --skip-crds

```

</div></div></div></div></div>### 第二階段：應用程式與路由套用

<div aria-busy="false" aria-live="polite" id="bkmrk-bash-%23-3.-%E9%83%A8%E7%BD%B2%E5%BE%8C%E7%AB%AF-pods-"><div aria-busy="false" aria-live="polite"><div data-hveid="0" data-ved="0CAAQhtANahgKEwjssI2WoeCTAxUAAAAAHQAAAAAQ4Qk"><div><span class="ng-tns-c1695782856-865">Bash</span><div><button aria-label="複製代碼" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger copy-button ng-tns-c1695782856-865 mat-unthemed ng-star-inserted"></button></div></div><div><div>```
<span class="hljs-comment"># 3. 部署後端 Pods 與 Services</span>
kubectl apply -f apps.yaml

<span class="hljs-comment"># 4. 部署 Gateway API 路由 (確保 Port 設為 8000)</span>
kubectl apply -f gateway-api.yml

```

</div></div></div>---

</div></div>## 3. 最終驗證與自動化分析

身為系統建設負責人，我們會使用以下腳本進行最後的數據收集，驗證權重分配是否精確。

### A. 檢查 Endpoints 狀態

確保每個版本都有對接到 Pod IP：

<div aria-busy="false" aria-live="polite" id="bkmrk-bash-kubectl-get-end"><div aria-busy="false" aria-live="polite"><div data-hveid="0" data-ved="0CAAQhtANahgKEwjssI2WoeCTAxUAAAAAHQAAAAAQ4gk"><div><span class="ng-tns-c1695782856-866">Bash</span><div><button aria-label="複製代碼" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger copy-button ng-tns-c1695782856-866 mat-unthemed ng-star-inserted"></button></div></div><div><div>```
kubectl get endpoints my-app-v1 my-app-v2

```

</div></div></div></div></div>### B. 執行金絲雀流量分析

<div aria-busy="false" aria-live="polite" id="bkmrk-bash-%23-%E5%9F%B7%E8%A1%8C-20-%E6%AC%A1%E6%B8%AC%E8%A9%A6%E4%B8%A6%E8%A8%88%E7%AE%97%E7%B5%90"><div aria-busy="false" aria-live="polite"><div data-hveid="0" data-ved="0CAAQhtANahgKEwjssI2WoeCTAxUAAAAAHQAAAAAQ4wk"><div><span class="ng-tns-c1695782856-867">Bash</span><div><button aria-label="複製代碼" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger copy-button ng-tns-c1695782856-867 mat-unthemed ng-star-inserted"></button></div></div><div><div>```
<span class="hljs-comment"># 執行 20 次測試並計算結果</span>
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> {1..20}; <span class="hljs-keyword">do</span> 
  curl -s -H <span class="hljs-string">"Host: localhost"</span> http://localhost | grep <span class="hljs-string">"Server name"</span>
<span class="hljs-keyword">done</span>

```

</div></div></div>---

</div></div>## 4. 維運經驗總結 (Lead's Summary)

這套全內容方案解決了您在實驗中遇到的所有核心技術挑戰：

<div aria-busy="false" aria-live="polite" id="bkmrk-%E5%9F%A0%E8%99%9F%E5%B0%8D%E9%BD%8A%EF%BC%9A%E8%A7%A3%E6%B1%BA%E4%BA%86-empty-reply"><div aria-busy="false" aria-live="polite">1. **埠號對齊**：解決了 `Empty reply` 與 `PortUnavailable` 問題。
2. **所有權衝突**：透過重置 Gateway 與正確使用 Helm 參數規避了 `SSA Conflict`。
3. **命名空間授權**：透過 `from: All` 解決了跨 Namespace 404 的問題。
4. **內容識別**：修正了 `grep` 關鍵字，確保能從 Nginx 輸出中精確捕捉版本差異。

</div></div>這份完整的清單已準備好，您可以直接將其納入專案的基礎設施維護手冊中。

# Laravel

# Laravel 建立專案

先安裝composer ，在切換到放置專案的目錄<textarea style="display: none;">composer global require laravel/installer laravel new example-app cd example-app php artisan serve</textarea>

```shell
composer global require laravel/installer

laravel new example-app

cd example-app

php artisan serve
```

修改參數

```PHP
//config/app.php

'timezone' => 'Asia/Taipei',
'locale' => 'zh_TW',
'faker_locale' => 'zh_TW',
```

加上Ｖue

[laravel8 + vue3.0 + element-plus 搭建 | IT人 (iter01.com)](https://iter01.com/590621.html)

[Laravel SPA with Vue 3, Auth (Sanctum), CRUD Example - Shouts](https://shouts.dev/laravel-spa-with-vue3-auth-crud-example)

# Laravel 常見問題: Specified key was too long

<header class="entry-header" id="bkmrk-laravel-%E5%B8%B8%E8%A6%8B%E5%95%8F%E9%A1%8C%3A-specif"><span style="color: #444444;">Laravel 在 5.4 版之後為了支援 emoji , 因此將資料編碼改為 utf8mb4. 由於 utf8mb4 的儲存空間需求膨脹了4倍, 導致預設長度無法正常寫入資料庫.</span></header>這個問題會在 MySQL 5.7.6 以下與 MariaDB 的環境中出現以下錯誤

<div id="bkmrk-%5Bpdoexception%5D-sqlst"><div>```
[PDOException]
SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes
```

</div></div>在 Migrate 過程中出現這個問題的解決方法有兩種

<div id="bkmrk-%E6%9B%B4%E6%96%B0%E5%88%B0-mysql-5.7.7-%E4%BB%A5%E4%B8%8A%E7%89%88%E6%9C%AC"><div>1. 更新到 MySQL 5.7.7 以上版本
2. 調整 Laravel 的預設資料長度

</div></div>升級 MySQL 的方法就不細說了, 接下來主要說明調整 Laravel 預設值的方法

首先, 編輯專案目錄下的 `app/Providers/AppServiceProvider.php`

新增一個引用

<div id="bkmrk-use-illuminate%5Csuppo"><div>```
use Illuminate\Support\Facades\Schema;
```

</div></div>接著修改 boot function, 將預設長度定為 191

<div id="bkmrk-public-function-boot"><div>```
public function boot()
{
    Schema::defaultStringLength(191);
}
```

</div></div>重新執行 `php artisan migrate` 就不會有問題了

最後附上完整的 AppServiceProvider.php

<div id="bkmrk-namespace-app%5Cprovid">```
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Schema;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Schema::defaultStringLength(191);
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}
```

</div>

# Laravel 解決在 CentOS 7 下 log 檔無法寫入的問題

在 `config/logging.php`

'permission' =&gt; 0775,

```JSON
'daily' => [
        'driver' => 'daily',
        'path' => storage_path('logs/laravel.log'),
        'level' => 'debug',
        'days' => 7,
        'permission' => 0775,
    ],
```

---

在非 homestead 的主機上執行 laravel 時，有時會出現空白畫面，或是以下的錯誤訊息：

```
The stream or file "/xxxx/yyyy/zzzz/storage/logs/laravel-<日期>.log" could not be opened: failed to open stream: Permission denied
```

這是因為我們使用不同的方式執行 laravel 程式，會讓 log 檔有不同權限設定，和沒有檔案的寫入權限。

內容目錄

<div id="bkmrk-%E8%A7%A3%E6%B3%95%E4%B8%80%EF%BC%9A%E5%B0%87-logs-%E7%9B%AE%E9%8C%84%E5%8F%8A%E6%AA%94%E6%A1%88%E6%AC%8A%E9%99%90%E7%9B%B4%E6%8E%A5"><div></div><nav>- [解法一：將 logS 目錄及檔案權限直接設為 777 (不推薦)](https://kirin.idv.tw/laravel-log-stream-open-failed-in-centos-7/#%E8%A7%A3%E6%B3%95%E4%B8%80%EF%BC%9A%E5%B0%87_logS_%E7%9B%AE%E9%8C%84%E5%8F%8A%E6%AA%94%E6%A1%88%E6%AC%8A%E9%99%90%E7%9B%B4%E6%8E%A5%E8%A8%AD%E7%82%BA_777_(%E4%B8%8D%E6%8E%A8%E8%96%A6) "解法一：將 logS 目錄及檔案權限直接設為 777 (不推薦)")
- [解法二：設計好適當的權限](https://kirin.idv.tw/laravel-log-stream-open-failed-in-centos-7/#%E8%A7%A3%E6%B3%95%E4%BA%8C%EF%BC%9A%E8%A8%AD%E8%A8%88%E5%A5%BD%E9%81%A9%E7%95%B6%E7%9A%84%E6%AC%8A%E9%99%90 "解法二：設計好適當的權限")
    - [步驟一：設定群組權限](https://kirin.idv.tw/laravel-log-stream-open-failed-in-centos-7/#%E6%AD%A5%E9%A9%9F%E4%B8%80%EF%BC%9A%E8%A8%AD%E5%AE%9A%E7%BE%A4%E7%B5%84%E6%AC%8A%E9%99%90 "步驟一：設定群組權限")
    - [步驟二：修改「網頁伺服器」的預設檔案存取權限](https://kirin.idv.tw/laravel-log-stream-open-failed-in-centos-7/#%E6%AD%A5%E9%A9%9F%E4%BA%8C%EF%BC%9A%E4%BF%AE%E6%94%B9%E3%80%8C%E7%B6%B2%E9%A0%81%E4%BC%BA%E6%9C%8D%E5%99%A8%E3%80%8D%E7%9A%84%E9%A0%90%E8%A8%AD%E6%AA%94%E6%A1%88%E5%AD%98%E5%8F%96%E6%AC%8A%E9%99%90 "步驟二：修改「網頁伺服器」的預設檔案存取權限")
    - [步驟三、修改「命令列」的預設檔案存取權限](https://kirin.idv.tw/laravel-log-stream-open-failed-in-centos-7/#%E6%AD%A5%E9%A9%9F%E4%B8%89%E3%80%81%E4%BF%AE%E6%94%B9%E3%80%8C%E5%91%BD%E4%BB%A4%E5%88%97%E3%80%8D%E7%9A%84%E9%A0%90%E8%A8%AD%E6%AA%94%E6%A1%88%E5%AD%98%E5%8F%96%E6%AC%8A%E9%99%90 "步驟三、修改「命令列」的預設檔案存取權限")
    - [步驟四、修改 「cron」 的預設檔案存取權限](https://kirin.idv.tw/laravel-log-stream-open-failed-in-centos-7/#%E6%AD%A5%E9%A9%9F%E5%9B%9B%E3%80%81%E4%BF%AE%E6%94%B9_%E3%80%8Ccron%E3%80%8D_%E7%9A%84%E9%A0%90%E8%A8%AD%E6%AA%94%E6%A1%88%E5%AD%98%E5%8F%96%E6%AC%8A%E9%99%90 "步驟四、修改 「cron」 的預設檔案存取權限")
    - [解法二總結](https://kirin.idv.tw/laravel-log-stream-open-failed-in-centos-7/#%E8%A7%A3%E6%B3%95%E4%BA%8C%E7%B8%BD%E7%B5%90 "解法二總結")
- [參考資料](https://kirin.idv.tw/laravel-log-stream-open-failed-in-centos-7/#%E5%8F%83%E8%80%83%E8%B3%87%E6%96%99 "參考資料")

</nav></div>## <span class="ez-toc-section" id="bkmrk--0"></span>解法一：將 logS 目錄及檔案權限直接設為 777 (不推薦)

<div id="bkmrk-chmod--r-777-storage">```php
chmod <span class="token operator">-</span>R <span class="token number">777</span> storage<span class="token operator">/</span>logs<span class="token operator">/</span>
```

<div><div>PHP</div></div></div>**這方法最快，但是只能治標，不能治本，有再度發生的可能；而且，還有安全性的隱憂**

## <span class="ez-toc-section" id="bkmrk--1"></span>解法二：設計好適當的權限

以 CentOS 為例，在 CentOS 中，我們以 apache 的 virtual host 來執行我們的 laravel 系統，會有三個比較常見的狀況：  
一、透過「網頁伺服器」執行程式  
二、透過「命令列」執行程式  
三、透過 「cron」 執行程式  
各會有不同的檔案權限規則應用在新產生的檔案。我們必須針對這三種方式，來規劃不同的權限規則。

### <span class="ez-toc-section" id="bkmrk--2"></span>步驟一：設定群組權限

由於 網頁伺服器的預設執行帳號是 apache，所以其產生的檔案，owner及group 均是 apache；而命令列或 cron，則會根據登入的使用者帳號來設定，為了要讓不同方式產生的檔案，能透過「群組權限」的設定值來存取程式，我們必需做一些設定，假設我們 virtual host 的網頁檔案是以 john 這個帳號來登入的

<div id="bkmrk-%23-%E6%8A%8A-john-%E5%8A%A0%E5%85%A5-apache-%E7%BE%A4">```bash
<span class="token comment"># 把 john 加入 apache 群組</span>
<span class="token function">usermod</span> -a -G apeche john
<span class="token comment"># 也把 apache 加入 john 群組</span>
<span class="token function">usermod</span> -a -G john apache
```

<div><div>Bash</div></div></div>**更改群組後，shell 要重新登入；部分服務需重新開機才能套用群組權限設定**

如果對於 Linux 的檔案權限不太熟悉，可以看一下[鳥哥的文章 - Linux 檔案與目錄管理](http://linux.vbird.org/linux_basic/0220filemanager.php#umask "鳥哥的文章 - Linux 檔案與目錄管理")。

### <span class="ez-toc-section" id="bkmrk--3"></span>步驟二：修改「網頁伺服器」的預設檔案存取權限

apache server 預設的執行帳號是 apache，產生的檔案權限為 644，範例如下：

```
-rw-r--r-- 1 apache apache    153438 12月 24 20:49 laravel-2019-12-24.log

```

我們必須設定 apache 的 umask 設定，讓其產生的檔案權限為 664，這樣一來，apache 群組才具有寫入的權限。

**設定檔 umask.conf 內容**

```
[Service]
UMask=0002
```

指令如下：

<div id="bkmrk-mkdir-%2Fetc%2Fsystemd%2Fs">```bash
<span class="token function">mkdir</span> /etc/systemd/system/httpd.service.d
<span class="token function">vi</span> /etc/systemd/system/httpd.service.d/umask.conf

systemctl daemon-reload
systemctl restart httpd.service
```

<div><div>Bash</div></div></div>### <span class="ez-toc-section" id="bkmrk--4"></span>步驟三、修改「命令列」的預設檔案存取權限

有時我們會透過 php artisan 來執行程式，產生的檔案, 其使用者和群組就是登入的帳號，而權限會根據 umask 來決定其權限。

<div id="bkmrk-%23-%E5%9C%A8-shell-%E4%B8%AD%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%BC%B8%E5%85%A5-uma">```bash
<span class="token comment"># 在 shell 中，直接輸入 umask 可以查看目前設定</span>
<span class="token function">umask</span>

<span class="token comment"># 修改 umask 為 0002</span>
<span class="token function">umask</span> 0002

<span class="token comment"># 將 umask 0002 的設定加入登入設定檔</span>
<span class="token function">vi</span> ~/.bashrc

```

<div><div>Bash</div></div></div>### <span class="ez-toc-section" id="bkmrk--5"></span>步驟四、修改 「cron」 的預設檔案存取權限

當透過 cron 來進行工作排程時，其所產生的檔案，owner 和 group 是登入帳號，預設的 umask 是，0022，我們可以透過修改設定檔來改變：

<div id="bkmrk-%23-%E4%BF%AE%E6%94%B9%E8%A8%AD%E5%AE%9A%E6%AA%94%EF%BC%8C%E5%9C%A8%5Bservice%5D-%E6%AE%B5">```bash
<span class="token comment"># 修改設定檔，在[Service] 段中，加入 UMask=0002</span>
<span class="token function">vi</span> /usr/lib/systemd/system/crond.service

systemctl daemon-reload
systemctl restart crond.service
```

<div><div>Bash</div></div></div>重新啟動 cron 服務後，其所產生的檔案權限就會變為 664

### <span class="ez-toc-section" id="bkmrk--6"></span>解法二總結

解法二的處理方式是要達到 2 個目標  
1、三種執行方式所產生的 log 檔案，不管 owner 是 apache 或是 john，其權限都是 664  
2、apache 和 john 這兩個帳號，都加入對方的群組中

來源：[https://kirin.idv.tw/laravel-log-stream-open-failed-in-centos-7/](https://kirin.idv.tw/laravel-log-stream-open-failed-in-centos-7/)

# Laravel jwt登入驗證

[Laravel API 系列教程（二）: 结合 Laravel 5.5 和 Vue SPA 基于 jwt-auth 实现 API 认证 | 构建 API 接口：原生开发 | Laravel 入门到精通教程 (laravelacademy.org)](https://laravelacademy.org/post/9178)

```PHP
 #config/app.php
   'providers' => [
	 #LaravelServiceProvider::class 要改成這個
        Tymon\JWTAuth\Providers\LaravelServiceProvider::class,
    ],

```

# Laravel migration 可使用的欄位類型

#### 可使用的欄位類型

資料庫 Schema 生成器包含表格常用的各種欄位類型，如下所列：

<table class="table table-striped" id="bkmrk-%E7%A8%8B%E5%BC%8F%E7%A2%BC-%E8%AA%AA%E6%98%8E-%24table-%3Eid%28%29%3B"><thead><tr><th>程式碼</th><th>說明</th></tr></thead><tbody><tr><td>$table-&gt;id();</td><td>$table-&gt;bigIncrements('id') 的別名</td></tr><tr><td>$table-&gt;foreignId('user\_id');</td><td>$table-&gt;unsignedBigInteger('user\_id') 的別名</td></tr><tr><td>$table-&gt;bigIncrements('id');</td><td>遞增 ID（主鍵），相當於「UNSIGNED BIG INTEGER」</td></tr><tr><td>$table-&gt;bigInteger('votes');</td><td>相當於 BIGINT</td></tr><tr><td>$table-&gt;binary('data');</td><td>相當於 BLOB</td></tr><tr><td>$table-&gt;boolean('confirmed');</td><td>相當於 BOOLEAN</td></tr><tr><td>$table-&gt;char('name', 100);</td><td>相當於帶有長度的 CHAR</td></tr><tr><td>$table-&gt;date('created\_at');</td><td>相當於 DATE</td></tr><tr><td>$table-&gt;dateTime('created\_at', 0);</td><td>相當於 DATETIME ，可以指定位數</td></tr><tr><td>$table-&gt;dateTimeTz('created\_at', 0);</td><td>相當於 DATETIME (帶時區) ，可以指定位數</td></tr><tr><td>$table-&gt;decimal('amount', 8, 2);</td><td>相當於 DECIMAL，可以指定總位數和小數位數</td></tr><tr><td>$table-&gt;double('amount', 8, 2);</td><td>相當於 DOUBLE，可以指定總位數和小數位數</td></tr><tr><td>$table-&gt;enum('level', \['easy', 'hard'\]);</td><td>相當於 ENUM</td></tr><tr><td>$table-&gt;float('amount', 8, 2);</td><td>相當於 FLOAT，可以指定總位數和小數位數</td></tr><tr><td>$table-&gt;geometry('positions');</td><td>相當於 GEOMETRY</td></tr><tr><td>$table-&gt;geometryCollection('positions');</td><td>相當於 GEOMETRYCOLLECTION</td></tr><tr><td>$table-&gt;increments('id');</td><td>遞增 ID（主鍵），相當於 UNSIGNED INTEGER</td></tr><tr><td>$table-&gt;integer('votes');</td><td>相當於 INTEGER</td></tr><tr><td>$table-&gt;ipAddress('visitor');</td><td>相當於 IP 地址</td></tr><tr><td>$table-&gt;json('options');</td><td>相當於 JSON</td></tr><tr><td>$table-&gt;jsonb('options');</td><td>相當於 JSONB</td></tr><tr><td>$table-&gt;lineString('positions');</td><td>相當於 LINESTRING</td></tr><tr><td>$table-&gt;longText('description');</td><td>相當於 LONGTEXT</td></tr><tr><td>$table-&gt;macAddress('device');</td><td>相當於 MAC 地址</td></tr><tr><td>$table-&gt;mediumIncrements('id');</td><td>遞增 ID（主鍵），相當於 UNSIGNED MEDIUMINT</td></tr><tr><td>$table-&gt;mediumInteger('votes');</td><td>相當於 MEDIUMINT</td></tr><tr><td>$table-&gt;mediumText('description');</td><td>相當於 MEDIUMTEXT</td></tr><tr><td>$table-&gt;morphs('taggable');</td><td>相當於加入遞增 UNSIGNED BIGINT 類型的 taggable\_id 與字串類型的 taggable\_type</td></tr><tr><td>$table-&gt;uuidMorphs('taggable');</td><td>相當於添加一個 CHAR (36) 類型的 taggable\_id 欄位和 VARCHAR (255) UUID 類型的 taggable\_type</td></tr><tr><td>$table-&gt;multiLineString('positions');</td><td>相當於 MULTILINESTRING</td></tr><tr><td>$table-&gt;multiPoint('positions');</td><td>相當於 MULTIPOINT</td></tr><tr><td>$table-&gt;multiPolygon('positions');</td><td>相當於 MULTIPOLYGON</td></tr><tr><td>$table-&gt;nullableMorphs('taggable');</td><td>添加一個可以為空版本的 morphs() 欄位</td></tr><tr><td>$table-&gt;nullableUuidMorphs('taggable');</td><td>添加一個可以為空版本的 uuidMorphs() 欄位</td></tr><tr><td>$table-&gt;nullableTimestamps(0);</td><td>timestamps() 方法的別名</td></tr><tr><td>$table-&gt;point('position');</td><td>相當於 POINT</td></tr><tr><td>$table-&gt;polygon('positions');</td><td>相當於 POLYGON</td></tr><tr><td>$table-&gt;rememberToken();</td><td>添加一個允許空值的 VARCHAR (100) 類型的 remember\_token 欄位</td></tr><tr><td>$table-&gt;set('flavors', \['strawberry', 'vanilla'\]);</td><td>相當於 SET</td></tr><tr><td>$table-&gt;smallIncrements('id');</td><td>遞增 ID（主鍵），相當於 UNSIGNED SMALLINT</td></tr><tr><td>$table-&gt;smallInteger('votes');</td><td>相當於 SMALLINT</td></tr><tr><td>$table-&gt;softDeletes('deleted\_at', 0);</td><td>相當於為軟刪除添加一個可為空值的 deleted\_at 欄位</td></tr><tr><td>$table-&gt;softDeletesTz('deleted\_at', 0);</td><td>相當於為軟刪除添加一個可為空值的帶時區的 deleted\_at 欄位</td></tr><tr><td>$table-&gt;string('name', 100);</td><td>相當於指定長度的 VARCHAR</td></tr><tr><td>$table-&gt;text('description');</td><td>相當於 TEXT</td></tr><tr><td>$table-&gt;time('sunrise', 0);</td><td>相當於指定位數的 TIME</td></tr><tr><td>$table-&gt;timeTz('sunrise', 0);</td><td>相當於指定位數帶時區的 TIME</td></tr><tr><td>$table-&gt;timestamp('added\_on', 0);</td><td>相當於指定位數的時間戳記</td></tr><tr><td>$table-&gt;timestampTz('added\_on', 0);</td><td>相當於指定位數帶時區的時間戳記</td></tr><tr><td>$table-&gt;timestamps(0);</td><td>相當於添加可為空值的時間戳記類型的 created\_at 和 updated\_at</td></tr><tr><td>$table-&gt;timestampsTz(0);</td><td>相當於添加指定時區的可為空值的時間戳記類型的 created\_at 和 updated\_at</td></tr><tr><td>$table-&gt;tinyIncrements('id');</td><td>相當於自動遞增 UNSIGNED TINYINT</td></tr><tr><td>$table-&gt;tinyInteger('votes');</td><td>相當於 TINYINT</td></tr><tr><td>$table-&gt;unsignedBigInteger('votes');</td><td>相當於 UNSIGNED BIGINT</td></tr><tr><td>$table-&gt;unsignedDecimal('amount', 8, 2);</td><td>相當於 UNSIGNED DECIMAL ，可以指定總位數和小數位數</td></tr><tr><td>$table-&gt;unsignedInteger('votes');</td><td>相當於 UNSIGNED INTEGER</td></tr><tr><td>$table-&gt;unsignedMediumInteger('votes');</td><td>相當於 UNSIGNED MEDIUMINT</td></tr><tr><td>$table-&gt;unsignedSmallInteger('votes');</td><td>相當於 UNSIGNED SMALLINT</td></tr><tr><td>$table-&gt;unsignedTinyInteger('votes');</td><td>相當於 UNSIGNED TINYINT</td></tr><tr><td>$table-&gt;uuid('id');</td><td>相當於 UUID</td></tr><tr><td>$table-&gt;year('birth\_year');</td><td>相當於 YEAR</td></tr></tbody></table>

[學習Laravel Migration，看這一篇就夠了 (pandalab.org)](https://pandalab.org/articles/105)

# Laravel queue 使用supervisor 實現多執行序

[https://segmentfault.com/a/1190000021165798](https://segmentfault.com/a/1190000021165798)

```
[program:laravel-queue-work]
process_name=%(program_name)s_%(process_num)02d
directory=/data/yoursite
command=php artisan queue:work
autostart=true
autorestart=true
user=www
numprocs=32
redirect_stderr=true
```

安裝supervisor

```shell
yum install -y supervisor
```

修改設定檔 vim /etc/supervisord.conf

```INI
[include]
files = supervisord.d/*.conf
;files = supervisord.d/*.ini 改成 files = supervisord.d/*.conf
```

新增 laravel-queue-work.conf

```shell
vim /etc/supervisord.d/laravel-queue-work.conf
```

```
[program:laravel-queue-work]
process_name=%(program_name)s_%(process_num)02d
directory=/usr/share/nginx/html/laravel5/webmon
command=php artisan queue:work
autostart=true
autorestart=true
user=nginx
numprocs=50
;stdout_logfile = /etc/supervisor.d/log/laravel-queue.log
;stderr_logfile = /etc/supervisor.d/log/laravel-queue_err.log
redirect_stderr=true

[supervisord]

```

常用指令

```shell
#執行 supervisor
#supervisord 
#supervisord -c /etc/supervisord.d/laravel-queue-work.conf (指令config)
[root@aaa]#  supervisord
/usr/lib/python2.7/site-packages/supervisor/options.py:461: UserWarning: Supervisord is running as root and it is searching for its configuration file in default locations (including its current working directory); you probably want to specify a "-c" argument specifying an absolute path to a configuration file for improved security.
  'Supervisord is running as root and it is searching '

#顯示執行 目前沒有執行
[root@a1-gcm12 etc]# supervisorctl status
unix:///var/run/supervisor/supervisor.sock no such file

#顯示執行 (目前執行)
[root@a1-gcm12 etc]# supervisorctl status
laravel-queue-work:laravel-queue-work_00   RUNNING   pid 28389, uptime 0:00:20
laravel-queue-work:laravel-queue-work_01   RUNNING   pid 28388, uptime 0:00:20
laravel-queue-work:laravel-queue-work_02   RUNNING   pid 28391, uptime 0:00:20



#修改設定檔後重新載入
[root@a1-gcm12 etc]# supervisorctl reload

#停用 supervisor
[root@aaa]# supervisorctl shutdown
Shut down


```

# Laravel Repository

[Day13-\[Laravel 資料夾目錄與內容\] Repository - iT 邦幫忙::一起幫忙解決難題，拯救 IT 人的一天 (ithome.com.tw)](https://ithelp.ithome.com.tw/articles/10205996)

# Laravel_解決CORS錯誤

已封鎖跨來源請求: 同源政策不允許讀取 http://192.168.1.1/api/XXXXXX 的遠端資源。（原因: 缺少 CORS 'Access-Control-Allow-Origin' 檔頭）

建立 app/Http/Middleware/Cors.php

```PHP
<?php

namespace App\Http\Middleware;

use Closure;

class Cors{
    public function handle($request, Closure $next)
    {
        return $next($request)->header('Access-Control-Allow-Origin', '*')
        ->header('Access-Control-Allow-Methods', '*')
        ->header('Access-Control-Allow-Headers', 'Origin, Methods, Content-Type, Authorization')
        ->header('Access-Control-Allow-Credentials', true);
    }

}

```

app/Http/Kenek.php

```PHP
protected $routeMiddleware = [
      //加入
        'cors' => \App\Http\Middleware\Cors::class,
    ];
protected $middlewarePriority = [
      //加入
        \App\Http\Middleware\Cors::class
    ];
```

路由加入middle ware

routes/api.php

```PHP
Route::group(['middleware' => 'cors'],function(){
    Route::match(['get','post'],'test/TestApi','TestController@TestApi');
});

```

# Laravel_log_permission

config/logging.php

```PHP
    'daily' => [
        'driver' => 'daily',
        'path' => storage_path('logs/laravel.log'),
        'level' => 'debug',
        'days' => 7,
        'permission' => 0664,
    ],
```

參考：[Laravel daily log created with wrong permissions | Newbedev](https://newbedev.com/laravel-daily-log-created-with-wrong-permissions)

# Laravel_Queue

[20分鐘學會Laravel的Queue功能 (pandalab.org)](https://www.pandalab.org/articles/107)

修改設定檔 env.php -&gt; 指定何種連接

queue.php 連接設定

[![image-1637202514068.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-11/scaled-1680-/mL8DFlh054owdEE9-image-1637202514068.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-11/mL8DFlh054owdEE9-image-1637202514068.png)

[![image-1637202726195.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-11/scaled-1680-/0ZD3mQBGlPv9OePm-image-1637202726195.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-11/0ZD3mQBGlPv9OePm-image-1637202726195.png)

建立table

```shell
# 建立 jobs table
php artisan queue:table
# 建立 failed-table
php artisan queue:failed-table

#  執行建立工作
php artisan migrate

```

建立job

```shell
#php artisan make:job {job name}
php artisan make:job TestGcm
```

# Laravel_Request

參考：

[學習LARAVEL 請求，看這一篇就夠了 (pandalab.org)](https://pandalab.org/articles/108)

# Laravel_Validation

參考:

[Laravel Validation 經驗談. 如何確認 request body的參數是符合我們預期的？… | by Kidd Chan | K88D | Medium](https://medium.com/kidd88/laravel-validation-%E7%B6%93%E9%A9%97%E8%AB%87-108902f61d09)

[Laravel 台灣翻譯文件 | Laravel 道場 (laravel-dojo.com)](https://docs.laravel-dojo.com/laravel/5.5/validation)

# 【Larvel】 redirect() 與 redirect()->intended()

在 Laravel 中，`redirect()->intended('/login')` 和 `redirect('/login')` 有一些差異，主要是用於不同的用途和情境：

1. **`redirect()->intended('/login')`**：
    
    
    - `intended()` 方法用於重定向到上一個受保護路由的預期 URL。這通常在使用中間件保護的路由中，當用戶未登入而被重定向到登入頁面時，用於指示登入成功後應該返回的路由。
    - 當用戶在未登入狀態下訪問一個需要登入才能訪問的路由時，系統會記錄下該受保護路由的 URL。當用戶成功登入後，使用 `redirect()->intended('/login')` 將用戶重定向到該受保護路由的 URL，這樣可以提供更好的使用者體驗。
    
    ```
    <span class="hljs-keyword">return</span> redirect<span class="hljs-function"><span class="hljs-params">()</span>-></span>intended(<span class="hljs-string">'/login'</span>);
    
    ```
2. **`redirect('/login')`**：
    
    
    - `redirect('/login')` 簡單地將用戶重定向到指定的 URL，這個 URL 可以是任何您希望將用戶重定向到的地方，例如登入頁面或任何其他頁面。
    
    ```
    <span class="hljs-function"><span class="hljs-keyword">return</span> <span class="hljs-title">redirect</span><span class="hljs-params">(<span class="hljs-string">'/login'</span>)</span></span>;
    
    ```

### 差異及適用情境：

- **使用 `redirect()->intended('/login')`**：
    
    
    - 主要用於處理登入成功後的重定向，確保用戶可以返回他們原本想要訪問但需要登入才能進入的頁面。
    - 通常與中間件（如 `auth` 中間件）結合使用，當用戶在未登入狀態下訪問受保護路由時會被重定向到登入頁面，成功登入後，會使用 `intended()` 方法將用戶重定向回原本要訪問的受保護路由。
- **使用 `redirect('/login')`**：
    
    
    - 簡單地將用戶重定向到指定的 URL，通常用於靜態的重定向，不涉及登入成功後的動態路由記錄和重定向。

### 總結：

使用 `redirect()->intended('/login')` 和 `redirect('/login')` 主要取決於您的需求。如果您需要確保用戶在成功登入後返回到原本要訪問的受保護路由，應使用 `intended()` 方法。而如果僅僅需要將用戶重定向到靜態的登入頁面或其他固定的 URL，則可以直接使用 `redirect('/login')`。

# Laravel 8 + Vue 3 專案建置（PHP 7.4 支援）

## 🔧 **專案名稱：DemoProject**

由於 PHP 7.4 無法使用 Vite，因此本指南使用 **Laravel Mix** 來配置 **Laravel 8 + Vue 3** 專案。

---

## ✅ **步驟 1：安裝 Laravel 8**

```bash
composer create-project laravel/laravel demo-project "8.*"
cd insure-next

```

啟動 Laravel 伺服器來確認專案正常運行：

```bash
php artisan serve

```

---

## ✅ **步驟 2：安裝 Vue 3**

在專案目錄下執行以下指令來安裝 Vue 3 和相關套件：

```bash
npm install vue@3 vue-loader@next

```

---

## ✅ **步驟 3：修改 Laravel Mix 設定**

開啟專案的 **`webpack.mix.js`**，修改如下：

```javascript
const mix = require('laravel-mix');

mix.js('resources/js/app.js', 'public/js')
    .vue()
    .sass('resources/sass/app.scss', 'public/css')
    .css('resources/css/app.css', 'public/css');

```

---

## ✅ **步驟 4：建立必要檔案**

1. **建立 `resources/js/app.js`**：

```javascript
import { createApp } from 'vue';
import App from './App.vue';

createApp(App).mount('#app');

```

2. **建立 `resources/js/App.vue`**：

```vue
<template>
    <div>
        <h1>Welcome to DemoProject!</h1>
    </div>
</template>

<script>
export default {
    name: 'App',
};
</script>

```

3. **建立 `resources/sass/app.scss`**（如果尚未建立）：

```bash
mkdir -p resources/sass

```

在 **`resources/sass/app.scss`** 中新增基本樣式：

```scss
body {
    font-family: Arial, sans-serif;
    background-color: #f8f9fa;
}

```

4. **建立 `resources/css/app.css`**（如果有純 CSS 檔案的需求）：

```css
/* app.css */
body {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

```

---

## ✅ **步驟 5：修改 Blade 模板**

將 **`resources/views/welcome.blade.php`** 修改為 **`resources/views/index.blade.php`**，內容如下：

```html

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>InsureNext</title>
    <link rel="stylesheet" href="{{ mix('css/app.css') }}">
</head>
<body>
    <div id="app"></div>

    <script src="{{ mix('js/app.js') }}"></script>
</body>
</html>

```

---

## ✅ **步驟 6：路由設定**

在 **`routes/web.php`** 中新增以下路由設定，將所有頁面導向 Vue 入口頁面：

```php
use Illuminate\Support\Facades\Route;

Route::get('/{any}', function () {
    return view('index');
})->where('any', '.*');

```

---

## ✅ **步驟 7：編譯資源**

執行以下指令來編譯前端資源：

```bash
npm run dev

```

若要進行生產環境的編譯，執行：

```bash
npm run production

```

---

## ✅ **錯誤排除 1：Module not found: Error: Can't resolve 'app.scss'**

如果你遇到以下錯誤：

```bash
Module not found: Error: Can't resolve 'resources/sass/app.scss'

```

請確保已建立 **`resources/sass/app.scss`**，或者修改 **`webpack.mix.js`**，移除 `sass` 編譯：

```javascript
const mix = require('laravel-mix');

mix.js('resources/js/app.js', 'public/js')
    .vue();

```

---

## ✅ **錯誤排除 2：Unable to locate Mix file: /css/app.css**

如果你遇到以下錯誤：

```bash
Unable to locate Mix file: /css/app.css

```

請依照以下步驟處理：

1. 確認資源已正確編譯：

```bash
npm run dev

```

2. 若問題仍未解決，清除 Laravel 快取：

```bash
php artisan config:clear
php artisan cache:clear
php artisan view:clear

```

3. 確認檔案 **`public/css/app.css`** 存在。如果不存在，執行以下指令重新建立檔案：

```bash
npm run dev

```

---

## ✅ **完成！**

現在你已經成功建置了一個 **Laravel 8 + Vue 3** 專案，專案名稱為 **InsureNext**。

Laravel 的路由設定已將所有頁面導向 **Vue 入口頁面**，並由 **Vue Router** 來處理前端頁面轉換。

</body></html>

# 【Laravel】部署在 SLB 或 Nginx 後面，要根據實際使用者請求是 HTTP 或 HTTPS

> 在 Laravel 中，當應用被部署在 **SLB 或 Nginx 後面**，要根據實際使用者請求是 HTTP 或 HTTPS，讓 Laravel 正確產生資源（asset/url）的協定 (`http://` 或 `https://`)，**同時支援兩種不同呼叫來源**（SLB 代理或 client 直連）。

---

## ✅ 關鍵目標：

Laravel 要根據 **實際瀏覽器請求的協定**（**而不是內部或 SLB 連線的協定**）來產生正確的資源連結。

---

## 🧠 為什麼 Laravel 判斷會錯？

當你部署在 **SLB 或 Nginx 反向代理**後面時，Laravel 實際收到的請求是從 SLB 傳來的，Laravel 預設只看：

```php
$_SERVER['HTTPS'] // on or off

```

這時它會誤以為是 HTTP，而不是使用者原本的 HTTPS。

---

## ✅ 正確作法：三步驟搞定 HTTP/HTTPS 自動判斷

---

### ✅ 步驟 1：設定 Laravel 的 TrustProxies

#### `app/Http/Middleware/TrustProxies.php`

```php
use Illuminate\Http\Request;
use Illuminate\Http\Middleware\TrustProxies as Middleware;

class TrustProxies extends Middleware
{
   protected $proxies = '*'; // 或指定你的 proxy IP

   // protected $headers = Request::HEADER_X_FORWARDED_ALL; // 此為舊方法已廢棄感用以下預設
  
   protected $headers =
        Request::HEADER_X_FORWARDED_FOR |
        Request::HEADER_X_FORWARDED_HOST |
        Request::HEADER_X_FORWARDED_PORT |
        Request::HEADER_X_FORWARDED_PROTO |
        Request::HEADER_X_FORWARDED_AWS_ELB;
}

```

---

### ✅ 步驟 2：讓 SLB 或 Nginx 傳遞 `X-Forwarded-Proto`

#### 📌 Nginx 範例（很關鍵）：

```nginx
location / {
    proxy_pass http://127.0.0.1:8000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;  # ⭐ 告訴 Laravel 真正協定是 http or https
}

```

#### 📌 AWS ALB / 阿里雲 SLB 也會自動加上 `X-Forwarded-Proto`，你只需要 Laravel 信任它即可（Step 1）

---

### ✅ 步驟 3：確認 `.env` 有設 APP\_URL（給 CLI 用）

```env
APP_URL=https://your-domain.com

```

Laravel CLI 執行時（如：`php artisan queue:work`）沒有 request context，會 fallback 使用 `APP_URL`。

---

## 🔍 驗證

在你的 Laravel 控制器或 middleware 中加這段：

```php
dd([
    'secure' => request()->isSecure(),
    'scheme' => request()->getScheme(),
    'url' => request()->fullUrl(),
]);

```

你應該會看到：

```php
[
  'secure' => true,
  'scheme' => 'https',
  'url' => 'https://your-domain.com/some/path'
]

```

或是在頁面上加上

```
 {{ request()->isSecure() ? 'HTTPS ✅' : 'HTTP ❌' }}
 <br>
 {{ url()->current() }}
 <br>
```

可以看到

[![image-1753348696262.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/scaled-1680-/Pnl4rW1ATQlkG0UN-image-1753348696262.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/Pnl4rW1ATQlkG0UN-image-1753348696262.png)

如果前方沒有正確帶入(nginx 前面還有SLB)，可以手動寫死

```Nginx
proxy_set_header X-Forwarded-Proto https;
```

---

## ✅ 小結：Laravel 正確產生 HTTP 或 HTTPS 協定連結的條件

<table id="bkmrk-%E9%A0%85%E7%9B%AE-%E8%AA%AA%E6%98%8E-trustproxies-%E8%A8%AD"><thead><tr><th>項目</th><th>說明</th></tr></thead><tbody><tr><td>`TrustProxies` 設定</td><td>必須設為 `*` + `HEADER_X_FORWARDED_ALL`</td></tr><tr><td>Proxy 傳 `X-Forwarded-Proto`</td><td>Nginx / SLB 必須傳遞使用者實際協定</td></tr><tr><td>`.env` 的 `APP_URL`</td><td>CLI 模式需 fallback 使用，請設為 `https://...`</td></tr><tr><td>使用 Laravel 的 `asset()`, `url()`, `route()`</td><td>Laravel 會根據協定正確產生 URL</td></tr></tbody></table>

---

# Ｍac

# 【Mac】清除dns快取

<table id="bkmrk-macos-version-comman"><thead><tr><th>MACOS VERSION</th><th>COMMAND</th></tr></thead><tbody><tr><td>macOS 12 (Monterey)</td><td>`sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder`</td></tr><tr><td>macOS 11 (Big Sur)</td><td>`sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder`</td></tr><tr><td>macOS 10.15 (Catalina)</td><td>`sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder`</td></tr><tr><td>macOS 10.14 (Mojave)</td><td>`sudo killall -HUP mDNSResponder`</td></tr><tr><td>macOS 10.13 (High Sierra)</td><td>`sudo killall -HUP mDNSResponder`</td></tr><tr><td>macOS 10.12 (Sierra)</td><td>`sudo killall -HUP mDNSResponder`</td></tr><tr><td>OS X 10.11 (El Capitan)</td><td>`sudo killall -HUP mDNSResponder`</td></tr><tr><td>OS X 10.10 (Yosemite)</td><td>`sudo discoveryutil udnsflushcaches`</td></tr><tr><td>OS X 10.9 (Mavericks)</td><td>`sudo killall -HUP mDNSResponder`</td></tr><tr><td>OS X 10.8 (Mountain Lion)</td><td>`sudo killall -HUP mDNSResponder`</td></tr><tr><td>Mac OS X 10.7 (Lion)</td><td>`sudo killall -HUP mDNSResponder`</td></tr><tr><td>Mac OS X 10.6 (Snow Leopard)</td><td>`sudo dscacheutil -flushcache`</td></tr><tr><td>Mac OS X 10.5 (Leopard)</td><td>`sudo lookupd -flushcache`</td></tr><tr><td>Mac OS X 10.4 (Tiger)</td><td>`lookupd -flushcache`</td></tr></tbody></table>

# 【Mac】brew 切換php版本

#### brew 安裝 特定 php 版本

```shell
#brew install php@{php version}
brew install php@7.3
```

#### brew 切換 php 版本

查詢目前版本

```shell
#-v 查詢版本
php -v

PHP 7.3.33 (cli) (built: Nov 24 2021 13:40:29) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.33, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.3.33, Copyright (c) 1999-2018, by Zend Technologies
```

切換版本

```shell
# 刪除php7.3 (連結，非相關檔案)
brew unlink php@7.3
# 連結php7.4
brew link php@7.4
```

如果有安裝zsh 要編輯.zshrc

```shell
#vim .zshrc 

export PATH=/usr/local/opt/php@7.4/bin:$PATH
export PATH=/usr/local/opt/php@7.4/sbin:$PATH

```

# 【Quicktime】影片合併分割

[超簡單！在 Mac 電腦裁切、分割、合併多段影片（Quicktime） – 重灌狂人 (briian.com)](https://briian.com/34302/)

# 【Ｍac】保持不休眠語法

終端機執行 caffeinate，直到中斷(Ctrl + c)之前不會休眠

```
caffeinate
```

```shell
#-t 秒數 保持喚醒時間
caffeinate -t 3600
```

# 【 Mac】安裝netdata

```Powershell
$ brew update
$ brew install netdata
$ brew services start netdata
```

```Powershell
# prometheus 格式
http://10.80.84.9:19999/api/v1/allmetrics?format=prometheus
```

# 【zsh】如何設定快捷鍵

編輯登入執行檔

```
vim ~/.zshrc 
```

```shell
# 加上你要設定的alias
alias ll="ls -alF"
```

重新載入或是關掉終端機再開

```shell
. ~/.zshrc
# 或是
source ~/.zshrc
```

# 【Mac】設定路由

### 添加路由

要添加一個新的靜態路由，你可以使用以下命令：

```lang-bash
sudo route -n add -net [目標網段] [網關IP]

```

- `[目標網段]`：你要添加的路由的目標網段。例如，`192.168.1.0/24`。
- `[網關IP]`：通往該目標網段的網關 IP 地址。

示例：

```lang-bash
sudo route -n add -net 192.168.1.0/24 192.168.1.1

```

這會將到達 `192.168.1.0/24` 的流量路由到網關 `192.168.1.1`。

### 刪除路由

要刪除一個已存在的路由，你可以使用以下命令：

```lang-bash
sudo route -n delete -net [目標網段]

```

示例：

```lang-bash
sudo route -n delete -net 192.168.1.0/24

```

這會刪除到達 `192.168.1.0/24` 的路由。

### 查看路由表

要查看當前的路由表，你可以使用：

```lang-bash
netstat -nr

```

或者

```lang-bash
route -n get default

```

這會列出當前所有的路由信息，包括網關、目標網段和路由標誌。

### 注意事項

- 使用 `sudo` 是必要的，因為更改網絡配置需要管理員權限。
- 添加的靜態路由通常不會在重啟後保留。如果需要持久化的路由設置，通常需要編輯網絡配置文件或使用網絡管理工具。
- 小心配置路由，以避免網絡連接問題。

# 【Mac】使用自簽憑證

### 產生憑證

在要放置自簽憑證資料夾

openssl req -x509 -out localhost.crt -keyout localhost.key -days 365 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' -extensions EXT -config &lt;( printf "\[dn\] \\nCN=localhost\\n\[req\]\\ndistinguished\_name = dn\\n\[EXT\]\\nsubjectAltName=DNS:localhost\\nkeyUsage=digitalSignature\\nextendedKeyUsage=serverAuth")

```shell
openssl req -x509 -out localhost.crt -keyout localhost.key -days 365 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' -extensions EXT -config <( printf "[dn] \nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")
```

產生憑證檔跟key

localhost.crt  
localhost.key

你可以使用 macOS 的 **Keychain Access（鑰匙圈存取）** 工具將 `localhost.crt` 憑證加入系統的信任憑證清單中。以下是詳細步驟：

---

### 【方法一】用 Terminal 安裝憑證到「系統」鑰匙圈

```bash
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain localhost.crt

```

#### 參數說明：

- `-d`：代表這是一個用來信任的憑證（不是用來簽署的憑證）。
- `-r trustRoot`：將憑證標記為信任根（Trust Root）。
- `-k /Library/Keychains/System.keychain`：安裝到系統層級的鑰匙圈（需要 `sudo` 權限）。
- `localhost.crt`：你生成的憑證檔案。

---

### 【方法二】手動用 GUI 加入（若你想看圖形介面）

1. 開啟 **「鑰匙圈存取」** (`Keychain Access`)
2. 選擇左上角「**系統**」鑰匙圈
3. 拖曳 `localhost.crt` 到右邊的憑證清單中
4. 右鍵點選剛剛新增的 `localhost` 憑證 → 點選「**取得資訊**」
5. 展開「**信任**」區塊
6. 將「**使用此憑證時**」設為「**永遠信任**」
7. 關閉視窗時，系統會要求你輸入管理員密碼以儲存變更

---

### 🧪 驗證是否已信任憑證

你可以使用以下方式確認憑證是否信任：

```bash
security verify-cert -c localhost.crt

```

或是直接在瀏覽器打開 `https://localhost`，若沒有「不受信任」或「警告」的訊息，就代表成功。

# 【Mac】固定網卡mac

**隨機化 MAC (Private Address, Random MAC)**

- 從 macOS Big Sur 開始，Wi-Fi 預設會啟用 **隨機 MAC** 功能，連接某些 SSID 時，系統會產生一個虛擬的 MAC，而不是用真實硬體的 MAC。
- 目的：避免被網路追蹤。
- 你會看到同一台 Mac 連不同 Wi-Fi，MAC 會不一樣。

[![image-1757485342036.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-09/scaled-1680-/xLE1hSFWujMLsjuJ-image-1757485342036.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-09/xLE1hSFWujMLsjuJ-image-1757485342036.png)

[![image-1757484944440.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-09/scaled-1680-/kol7wp9ORb5x60aC-image-1757484944440.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-09/kol7wp9ORb5x60aC-image-1757484944440.png)

# 【Mac】指令開關wifi - lan

vim ~/.zshrc

```shell
# networksetup -listallhardwareports 查詢介面

export ETH_IF="en0"    # 實體網卡
export WIFI_IF="en1"   # Wi-Fi

net_to_wifi() {
  sudo ifconfig "$ETH_IF" down
  sudo networksetup -setairportpower "$WIFI_IF" on
  sudo dscacheutil -flushcache
  sudo killall -HUP mDNSResponder
}

net_to_lan() {
  sudo ifconfig "$ETH_IF" up
  sudo networksetup -setairportpower "$WIFI_IF" off
  sudo dscacheutil -flushcache
  sudo killall -HUP mDNSResponder
}

alias towifi='net_to_wifi'
alias tolan='net_to_lan'
```

source ./zshrc

# 【Mac】查詢dns

```shell
scutil --dns
```

在 mac 上要看「目前正在用哪幾個 DNS」，最準的是看系統實際套用到每個網路介面的設定（Wi-Fi / 有線可能不同）。你可以用下面幾種方式：

## 1) GUI 方式（最直覺）

1. **系統設定** → **網路**
2. 選你正在用的介面（例如 **Wi-Fi** 或 **乙太網路**）
3. 點 **詳細資訊 / 進階** → **DNS**
4. 你會看到 **DNS 伺服器**清單（目前套用的）

> 這裡顯示的是你手動填的 + DHCP 下發的（視版本介面不同）

## 2) CLI 方式（最準，直接看目前介面）

### 看整台 Mac 所有網路服務的 DNS（推薦）

```bash
scutil --dns

```

你會看到多組 `resolver #`，裡面有 `nameserver[0] : x.x.x.x`  
通常 **優先看 `order` 最小**、以及 `if_index` 對應你正在用的介面那組。

### 只看某個網路服務（例如 Wi-Fi / Ethernet）

先列出網路服務名稱：

```bash
networksetup -listallnetworkservices

```

再看某個服務的 DNS（例如 Wi-Fi）：

```bash
networksetup -getdnsservers "Wi-Fi"

```

乙太網路則可能叫：

```bash
networksetup -getdnsservers "Ethernet"

```

## 3) 快速判斷「目前預設走哪個介面」

```bash
route get default | grep interface

```

會回傳像 `interface: en0`（通常 en0 可能是有線或 Wi-Fi，依機型/設定而定）

---

# 【除錯】【Mac】crontab 無法正常執行

在 macOS 環境下，`crontab` 沒執行的原因通常與 Linux 大不相同，最常見的元凶是 **macOS 嚴格的隱私權與安全性限制（TCC）**，其次是**排程時間定義**或**環境變數路徑**問題。

以下是排查 macOS Crontab 沒有執行（或執行失敗）的精準步驟：

### 原因一：macOS 系統權限封鎖（最常見原因）

從 macOS Mojave (10.14) 開始，系統引入了 TCC 隱私保護。**`cron` 程式預設沒有權限讀取使用者的家目錄（如 `/Users/srou/shell/...`）**。當排程時間到了，`cron` 試圖去讀取腳本時，會直接被系統阻擋（Permission denied），而且這種阻擋**不會**寫入您的自訂 log 中。

#### 🛠️ 解決步驟：

<div aria-busy="false" aria-live="polite" id="bkmrk-%E6%89%93%E9%96%8B-mac-%E7%9A%84-%E3%80%8C%E7%B3%BB%E7%B5%B1%E8%A8%AD%E5%AE%9A%E3%80%8D-%28sys"><div aria-busy="false" aria-live="polite">1. 打開 Mac 的 **「系統設定」 (System Settings)**。
2. 進入 **「隱私權與安全性」 (Privacy &amp; Security)** -&gt; **「全權硬碟存取權」 (Full Disk Access)**。
3. 檢查清單中是否有 **`cron`**。
4. 如果沒有（通常預設沒有）：
    
    
    - 點擊下方的 **「+」** 號。
    - 按下 `Command + Shift + G` 彈出路徑輸入框。
    - 輸入 `/usr/sbin/cron` 並點選開啟。
5. 將 `cron` 的開關切換為開啟（允許）狀態。

</div></div>### 原因二：Crontab 時間格式設定不符合預期

您的設定為：

<div aria-busy="false" aria-live="polite" id="bkmrk-%2A%2F5-%2A-%2A-%2A-1-5-%2Fusers"><div aria-busy="false" aria-live="polite"><div data-hveid="0" data-ved="0CAAQhtANahgKEwi46t3YrMSUAxUAAAAAHQAAAAAQuwI">```
*/5 * * * 1-5 /Users/AAA/shell/clean_mangodb.sh 2025-02-20

```

</div>- **`*/5`**：每 5 分鐘執行一次。
- **`1-5`**：週一至週五（Monday to Friday）。
- **今天是 2026 年 5 月 19 日（星期二）**，時間符合週一至週五。
- **排查點**：請確認您測試當下的時間是否在週一至週五。如果是週末測試，它確實不會觸發。

</div></div>### 原因三：沒有擷取錯誤輸出，導致失敗卻看不到

目前您的 `clean_mangodb.sh` 這一行**沒有加上錯誤重導向**。如果腳本在啟動時就因為路徑或權限問題掛掉，您完全無法得知原因。

#### 🛠️ 解決步驟：

請修改 crontab，將標準錯誤輸出（stderr）導向暫存檔：

<div aria-busy="false" aria-live="polite" id="bkmrk-%E8%BC%B8%E5%85%A5-crontab--e-%E5%B0%87%E8%A9%B2%E8%A1%8C%E4%BF%AE%E6%94%B9%E7%82%BA"><div aria-busy="false" aria-live="polite">1. 輸入 `crontab -e`
2. 將該行修改為（加上 `>> /tmp/clean.log 2>&1`）：
    
    <div data-hveid="0" data-ved="0CAAQhtANahgKEwi46t3YrMSUAxUAAAAAHQAAAAAQvAI"><div><div>```
    */5 * * * 1-5 /Users/AAA/shell/clean_mangodb.sh 2025-02-20 >> /tmp/clean.log 2>&1
    
    ```
    
    </div></div></div>
3. 儲存離開後，靜待下一個 5 分鐘，然後查看 `/tmp/clean.log` 的內容。

</div></div>### 原因四：macOS `cron` 服務未啟動

雖然 macOS 預設會啟動 `cron`，但有時可能被關閉。macOS 負責管理後台服務的是 `launchd`。

#### 🛠️ 檢查與重啟步驟：

檢查 `cron` 是否在運行：

<div aria-busy="false" aria-live="polite" id="bkmrk-sudo-launchctl-list-"><div aria-busy="false" aria-live="polite"><div data-hveid="0" data-ved="0CAAQhtANahgKEwi46t3YrMSUAxUAAAAAHQAAAAAQvQI"><div><div>```
sudo launchctl list | grep cron

```

</div></div></div></div></div>如果沒有輸出，或是您想強制重載 `cron` 服務，請執行：

<div aria-busy="false" aria-live="polite" id="bkmrk-%23-%E5%81%9C%E6%AD%A2%E6%9C%8D%E5%8B%99-sudo-launchct"><div aria-busy="false" aria-live="polite"><div data-hveid="0" data-ved="0CAAQhtANahgKEwi46t3YrMSUAxUAAAAAHQAAAAAQvgI"><div><div><button aria-label="複製代碼" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-badge mat-unthemed mat-badge-overlap mat-badge-above mat-badge-after mat-badge-small mat-badge-hidden ng-star-inserted"></button></div></div>```
<span class="hljs-comment"># 停止服務</span>
sudo launchctl unload /System/Library/LaunchDaemons/com.vix.cron.plist 2>/dev/null
<span class="hljs-comment"># 重新啟動服務</span>
sudo launchctl load /System/Library/LaunchDaemons/com.vix.cron.plist

```

</div></div></div>### 原因五：環境變數（PATH）問題

`cron` 執行時的環境變數非常乾淨，通常 `PATH` 只有 `/usr/bin:/bin`。

如果您的 `clean_mangodb.sh` 腳本內容中呼叫了某些透過 Homebrew 安裝的工具（雖然您目前主要使用 `curl`、`sed`，這些在 `/usr/bin` 都有），但若腳本有用到 `/usr/local/bin` 或 `/opt/homebrew/bin` 的套件，就會噴出 `command not found`。

<div aria-busy="false" aria-live="polite" id="bkmrk-%E9%A9%97%E8%AD%89%E6%96%B9%E5%BC%8F%EF%BC%9A%E9%80%8F%E9%81%8E%E4%B8%8A%E8%BF%B0%E5%8E%9F%E5%9B%A0%E4%B8%89%E5%8A%A0%E4%B8%8A-2%3E%261-"><div aria-busy="false" aria-live="polite">- **驗證方式**：透過上述**原因三**加上 `2>&1` 導出 log 後，最容易看出來是否卡在找不到指令。

</div></div>### 📋 建議排查順序

<div aria-busy="false" aria-live="polite" id="bkmrk-%E5%85%88%E5%8E%BB-%E5%85%A8%E6%AC%8A%E7%A1%AC%E7%A2%9F%E5%AD%98%E5%8F%96%E6%AC%8A-%E8%A3%9C%E4%B8%8A-%2Fusr%2Fs">1. 先去 **全權硬碟存取權** 補上 `/usr/sbin/cron` 的權限（90% 是這個問題）。
2. 用 `crontab -e` 把日誌重導向 `>> /tmp/clean.log 2>&1` 補上。
3. 檢查 `/tmp/clean.log` 的報錯訊息。

</div>

# Mantis

# 【Mantis】相關連結

- [api document](https://documenter.getpostman.com/view/29959/mantis-bug-tracker-rest-api/7Lt6zkP)

# MongoDB

# 【Mac】安裝MongoDB

```
brew tap mongodb/brew
```

```
brew update
```

```
brew install mongodb-community@6.0
```

[https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-os-x/](https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-os-x/)

---

或是使用docker-composer

\*docker-compose.yml

```YAML
version: '3.1'

services:

  mongo:
    image: mongo:6.0
    restart: always
    container_name: mongo1
    volumes:
      - ./data:/data/db:/data/db
    ports:
      - 27017:27017
```

```shell
#起動 docker
docker-compose up

```

# 常用指令

使用mongosh 連入db

```shell
srou@momo-full-devdeiMac ~ % mongosh
Current Mongosh Log ID:	63de0489619b4a08812f03ee
Connecting to:		mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+1.6.2
Using MongoDB:		6.0.4
Using Mongosh:		1.6.2

For mongosh info see: https://docs.mongodb.com/mongodb-shell/

------
   The server generated these startup warnings when booting
   2023-02-04T06:35:33.363+00:00: Access control is not enabled for the database. Read and write access to data and configuration is unrestricted
   2023-02-04T06:35:33.363+00:00: vm.max_map_count is too low
------

test> 

```

```JSON
db.order.aggregate([
  	{ 	$match : {
    		liveId: "aaaa",
      		orderDate: { $gt:ISODate("2023-11-01T23:00:00+08:00")}
    	}
    },
    {
        $group: {
            _id: {
                orderNo: "$orderNo",
                liveId: "$liveId"
            },
            count: { $sum: 1 }
        }
    },
    {
        $match: {
            count: { $gte: 2 }
        }
    },
  {
    $sort: { count : -1}
  },
  {
  	$limit:5
  }
])
/////////////////////////////////////////
select count(orderNo), count(liveId) 
from order 
group by orderNo, liveId 
order by count(liveId)
having count(*) >= 2
limit 5
             

```

# MongoDB教育訓練-01

####  

#### mongodb 儲存格式 是 Bson

[![image-1639619984280.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/oxUMylTkL0FwEXyZ-image-1639619984280.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/oxUMylTkL0FwEXyZ-image-1639619984280.png)

---

#### MongoDB 與關連式資料庫結構對照

---

[![image-1639620745002.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/W9Lmzan35b3QRvxu-image-1639620745002.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/W9Lmzan35b3QRvxu-image-1639620745002.png)

---

##### 企業版的Tool

---

[![image-1639621669047.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/JAqoDNlA6CbxSY8E-image-1639621669047.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/JAqoDNlA6CbxSY8E-image-1639621669047.png)

---

#### 何時使用ＭongoDB

- 需要存取資料
- 需要很大的數據
- 需要快速迭代開發
- 需要經常異動結構的資料
- 需要水平擴展

[![image-1639621823585.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/lXhirUMxp8JUe5I6-image-1639621823585.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/lXhirUMxp8JUe5I6-image-1639621823585.png)

[![image-1639621986744.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/tMwkeefhczCy8lgJ-image-1639621986744.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/tMwkeefhczCy8lgJ-image-1639621986744.png)

[![image-1639622039228.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/elifzXOUWxBiD6wh-image-1639622039228.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/elifzXOUWxBiD6wh-image-1639622039228.png)

---

#### MongoDB 與聯式資料庫對比

---

[![image-1639622524198.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/F2XJZ4J4HPuEp8Y6-image-1639622524198.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/F2XJZ4J4HPuEp8Y6-image-1639622524198.png)

[![image-1639622553219.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/9g5qPu1DqUbrjfJQ-image-1639622553219.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/9g5qPu1DqUbrjfJQ-image-1639622553219.png)

[![image-1639622880932.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/tHfDGn5LL3Xu3W0v-image-1639622880932.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/tHfDGn5LL3Xu3W0v-image-1639622880932.png)

# MongoDB教育訓練-02

[![image-1639624442610.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/3k1ivaJGMWhtW5tT-image-1639624442610.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/3k1ivaJGMWhtW5tT-image-1639624442610.png)

註冊

[https://cloud.mongodb.com/](https://cloud.mongodb.com/)

tool

https://www.mongodb.com/try/download/shell

[![image-1639625251215.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/DXUNYmjkudfFbzFL-image-1639625251215.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/DXUNYmjkudfFbzFL-image-1639625251215.png)

[![image-1639625277499.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/aGAaOBHIp8Oh9CNU-image-1639625277499.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/aGAaOBHIp8Oh9CNU-image-1639625277499.png)

### 1.新增帳號

[![image-1639625301233.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/NLdjaVAFQFaPwxnX-image-1639625301233.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/NLdjaVAFQFaPwxnX-image-1639625301233.png)

### 2.新增你的存取IP-&gt; 【add My Current Ip address】

[![image-1639625403123.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/r3no6agALJXsY2RP-image-1639625403123.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/r3no6agALJXsY2RP-image-1639625403123.png)

### 3.下載sample資料庫

[![image-1639626142620.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/WnivN3wogBd9wSxH-image-1639626142620.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/WnivN3wogBd9wSxH-image-1639626142620.png)

[![image-1639626286659.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/iLF5QzfcZF7ipXp7-image-1639626286659.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/iLF5QzfcZF7ipXp7-image-1639626286659.png)

#### 下載中.....

[![image-1639626839316.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/2coyhuTSbwCCNGcy-image-1639626839316.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/2coyhuTSbwCCNGcy-image-1639626839316.png)

#### 已下載完成

[![image-1639626966774.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/wIqc6xXs7smVRpZJ-image-1639626966774.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/wIqc6xXs7smVRpZJ-image-1639626966774.png)

###  

---

### 4.新增連線 【Connect】

[![image-1639625864493.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/9uGFPUTDgwZL09pv-image-1639625864493.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/9uGFPUTDgwZL09pv-image-1639625864493.png)

### Connect with the MongoDB Shell

### [![image-1639625922470.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/32XCdwdH7eL1qDbx-image-1639625922470.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/32XCdwdH7eL1qDbx-image-1639625922470.png)

[![image-1639626096319.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/BeiGLEI11ItjPM37-image-1639626096319.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/BeiGLEI11ItjPM37-image-1639626096319.png)

#### Mac 安裝

```shell
brew install mongosh
```

#### 連線(依照網頁url)

```shell
mongosh "mongodb+srv://cluster0.0roj8.mongodb.net/myFirstDatabase" --username admin
```

[![image-1639626216471.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/NpReneOzL4k6Ct01-image-1639626216471.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/NpReneOzL4k6Ct01-image-1639626216471.png)

# MongoDB教育訓練-03

[![image-1639632341110.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/94lf1AMIPzr4hZtu-image-1639632341110.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/94lf1AMIPzr4hZtu-image-1639632341110.png)

[![image-1639632437556.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/WKjief5bW4SVievQ-image-1639632437556.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/WKjief5bW4SVievQ-image-1639632437556.png)

count 儘量不要用

db.inspections.countDocuments() #精確查詢(1)

db.inspections.count() #

[![image-1639632701684.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/PvIPPDc1MJeJEHO7-image-1639632701684.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/PvIPPDc1MJeJEHO7-image-1639632701684.png)

[![image-1639632747819.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/FmITDA5ZEs1EcJLL-image-1639632747819.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/FmITDA5ZEs1EcJLL-image-1639632747819.png)

### insertOne 插入一筆資料

```shell
#插入一條資料
insertOne 
```

[![image-1639633018479.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/nPd523UFpm9GLEdp-image-1639633018479.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/nPd523UFpm9GLEdp-image-1639633018479.png)

id 本身會包含時間

```shell
getTimestamp #取得時間
```

[![image-1639633066887.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/ezNU46uA4bVaj8YV-image-1639633066887.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/ezNU46uA4bVaj8YV-image-1639633066887.png)

---

### insertMany 插入多筆資料

---

[![image-1639633247450.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/PK8p90XrwmMYMVob-image-1639633247450.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/PK8p90XrwmMYMVob-image-1639633247450.png)

---

### 有序插入＆無序插入

{ordered: true | false}

- 有序可保證順序，速度較慢，中間錯誤會全部返回
- 使用無序不影響，中間錯誤紀錄

[![image-1639633329359.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/6t8AKkTj2D95v1K7-image-1639633329359.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/6t8AKkTj2D95v1K7-image-1639633329359.png)

---

### 搜尋

- find()
- findOne()

[![image-1639633549742.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/muIv1v567N87Gkzz-image-1639633549742.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/muIv1v567N87Gkzz-image-1639633549742.png)

[![image-1639633608684.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/cLLolKcRHsNGZ2oS-image-1639633608684.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/cLLolKcRHsNGZ2oS-image-1639633608684.png)

---

### 返回特定欄位

[![image-1639633633419.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/KbQT1216Sq9SAD2i-image-1639633633419.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/KbQT1216Sq9SAD2i-image-1639633633419.png)

[![image-1639633835708.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/hlXc6ACses2PRdDQ-image-1639633835708.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/hlXc6ACses2PRdDQ-image-1639633835708.png)

Find 搜尋20筆資料

```shell
for(var i = 0; i < 200; i++) db.taxis.insertOne({plate: i})

# plate = 5 
db.taxis.find({plate:5})

# plate >= 5 
db.taxis.find({plate:{$gt: 5}})

#下20筆
it 

```

[![image-1639633889739.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/HAJKD0YI6qjXbzCS-image-1639633889739.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/HAJKD0YI6qjXbzCS-image-1639633889739.png)

[![image-1639634201304.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/S6OTyAfpsm9EnT7f-image-1639634201304.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/S6OTyAfpsm9EnT7f-image-1639634201304.png)

[![image-1639634238534.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/BqQDUaktlTiyjEE0-image-1639634238534.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/BqQDUaktlTiyjEE0-image-1639634238534.png)

[![image-1639634282001.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/SHspsUv1MESPkWVs-image-1639634282001.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/SHspsUv1MESPkWVs-image-1639634282001.png)

[![image-1639634354192.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/0eB5sNzuWeFblYm5-image-1639634354192.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/0eB5sNzuWeFblYm5-image-1639634354192.png)

curosr 10分鐘會被丟棄

[![image-1639634486948.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/HqauJyTiUugDrl2S-image-1639634486948.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/HqauJyTiUugDrl2S-image-1639634486948.png)

[![image-1639634849381.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/4WaVjcgxz6nUFEpi-image-1639634849381.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/4WaVjcgxz6nUFEpi-image-1639634849381.png)

```shell
db.diaries.drop()

db.diaries.insertMany([
{ name: "dug", day: ISODate("2014-11-04"), txt: "went for a walk"},
{ name: "dug", day: ISODate("2014-11-06"), txt: "saw a squirrel"},
{ name: "ray", day: ISODate("2014-11-06"), txt: "met dug in the park"},
{ name: "dug", day: ISODate("2014-11-09"), txt: "got a treat"}
])

#找出 name = dug

#輸出 name, text

```

---

### 子查詢

```shell
# city = "New York" 即可
address.city :"New York"
#{ city:"New York"} 完整匹配
address : { city:"New York"}} 
```

[![image-1639635141931.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/OojZsQyA1JD09N0q-image-1639635141931.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/OojZsQyA1JD09N0q-image-1639635141931.png)

---

### 範圍查詢

[![image-1639635381525.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/XfYn3OcXjJzF0Uim-image-1639635381525.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/XfYn3OcXjJzF0Uim-image-1639635381525.png)

---

### Boolen 查詢

```SQL
$or : [ {species:"cat", color:"black"},
		{species:"dog", color:"brown"}}]
#等於

where (species = "cat" and color = "black") or (species = "dog" and color = "brown")
```

[![image-1639635516284.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/jSZrykdfTp5dpaZ5-image-1639635516284.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/jSZrykdfTp5dpaZ5-image-1639635516284.png)

[![image-1639635768002.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/skJ9wrMqTynMI6U1-image-1639635768002.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/skJ9wrMqTynMI6U1-image-1639635768002.png)

```JavaScript
db.grades.find({ student_id : {$lt: 65}})

db.inspections.find({$or :[ {result:"Pass"},{result:"Fail"}]})
```

# MongoDB教育訓練-04

<div id="bkmrk-"></div>### Array 查詢

[![image-1639636664729.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/orQuFRgAB53eZmlc-image-1639636664729.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/orQuFRgAB53eZmlc-image-1639636664729.png)

Array 查詢 操作

- $all
- $size(少用無法使用index)
- $element 元素滿足

[![image-1639636792202.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/fE5DFJqTbqsCgw85-image-1639636792202.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/fE5DFJqTbqsCgw85-image-1639636792202.png)

```shell
#age > = 18 && age <= 3 , array 其中一個值滿足其中一個條件即可
age : { $gt : 18, $lt: 30}

#age > = 18 && age <= 3 , array 其中一個值滿足所有條件
age : { $elemMatch { $gt : 18, $lt: 30}}
```

[![image-1639636930873.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/Zck5NBmCJvnWMS4V-image-1639636930873.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/Zck5NBmCJvnWMS4V-image-1639636930873.png)

[![image-1639637035674.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/ozw8oxnaZmpBD8QU-image-1639637035674.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/ozw8oxnaZmpBD8QU-image-1639637035674.png)

[![image-1639637083583.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/Q66Txy8VQ3dAAJTk-image-1639637083583.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/Q66Txy8VQ3dAAJTk-image-1639637083583.png)

---

### 排序

[![image-1639637186726.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/jL5gOADRIlPthog5-image-1639637186726.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/jL5gOADRIlPthog5-image-1639637186726.png)

[![image-1639637211917.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/Al2q4DEcAzFIr5x4-image-1639637211917.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/Al2q4DEcAzFIr5x4-image-1639637211917.png)

```JavaScript
_id: 0 #不顯示id
```

---

[![image-1639637453427.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/r3xpI3omOeXaZ0NI-image-1639637453427.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/r3xpI3omOeXaZ0NI-image-1639637453427.png)

---

### 更新

- updateOne : 更新查詢第一筆
- updateMany: 更新所有

[![image-1639637602587.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/yjByIjfU24o6QG8H-image-1639637602587.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/yjByIjfU24o6QG8H-image-1639637602587.png)

### 更新欄位

[![image-1639637827684.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/bdVrg0JxuMzGLh08-image-1639637827684.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/bdVrg0JxuMzGLh08-image-1639637827684.png)

[![image-1639637871930.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/VRZtnZN7od3TLmQT-image-1639637871930.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/VRZtnZN7od3TLmQT-image-1639637871930.png)

```shell
# 加減
$inc : +
# 乘
$mul: *
```

[![image-1639637930449.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/GaZKRKlrgtwLVKc6-image-1639637930449.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/GaZKRKlrgtwLVKc6-image-1639637930449.png)

```shell
#大於更新
$max
#小於才更新
$min 
```

[![image-1639638023450.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/YIpGvQK6qx2wj20t-image-1639638023450.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/YIpGvQK6qx2wj20t-image-1639638023450.png)

[![image-1639638110007.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/OJhetSgj9ZPNc72l-image-1639638110007.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/OJhetSgj9ZPNc72l-image-1639638110007.png)

```shell
#q1
db.inspections.updateMany({result:"Completed"},{$set:{"result":"No Violation Issued"}})
#q2
db.inspections.updateMany({result:'Fail'},{$set:{fine:100}})
#q3
db.inspections.updateMany({"address.city": "ROSEDALE"}, {$inc: {fine: 150}})
```

---

### Array 新增移除

```
$pop : 1 (最後元素)
$pop : -1 (第一個元素)

# 檢查不存在才新增
$addToSet
```

[![image-1639638757601.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/NvJZxjfM99yuQTFD-image-1639638757601.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/NvJZxjfM99yuQTFD-image-1639638757601.png)

[![image-1639638822064.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/hd5Z3nbzmaof7vlr-image-1639638822064.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/hd5Z3nbzmaof7vlr-image-1639638822064.png)

[![image-1639638913289.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/bLM6lJ8hzRL8DBOp-image-1639638913289.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/bLM6lJ8hzRL8DBOp-image-1639638913289.png)

[![image-1639638947708.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/mPpEqgSYth8HLQIF-image-1639638947708.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/mPpEqgSYth8HLQIF-image-1639638947708.png)

基本mongoDB 基本無lock，每次更新檢更新序號，是否為最新，若不是最新，嘗試合併最新，重新更新

[![image-1639639033397.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/pQCq9IUqhPqAlcuE-image-1639639033397.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/pQCq9IUqhPqAlcuE-image-1639639033397.png)

# MongoDB教育訓練-05

[![image-1639640556450.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/3sgHFnkOoHgAfKv0-image-1639640556450.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/3sgHFnkOoHgAfKv0-image-1639640556450.png)

---

### 更新所有陣列元素

[![image-1639640396053.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/5lmZP0UTk0LeoiX2-image-1639640396053.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/5lmZP0UTk0LeoiX2-image-1639640396053.png)

[![image-1639640527546.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/kMqpqa5yjRU052vs-image-1639640527546.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/kMqpqa5yjRU052vs-image-1639640527546.png)

---

[![image-1639640566773.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/SfSj67qNxS0Og62P-image-1639640566773.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/SfSj67qNxS0Og62P-image-1639640566773.png)

[![image-1639640635519.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/TRpqG0HqBVwRqOjP-image-1639640635519.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/TRpqG0HqBVwRqOjP-image-1639640635519.png)

[![image-1639640671909.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/CN7aSkpj4av8t6pn-image-1639640671909.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/CN7aSkpj4av8t6pn-image-1639640671909.png)

[![image-1639640727694.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/luynlVajG4Z0613P-image-1639640727694.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/luynlVajG4Z0613P-image-1639640727694.png)

---

### 子查詢更新

[![image-1639640872931.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/IrUDJD3mUnixhPZU-image-1639640872931.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/IrUDJD3mUnixhPZU-image-1639640872931.png)

[![image-1639641079436.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/KADc5o84b4xZDYqF-image-1639641079436.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/KADc5o84b4xZDYqF-image-1639641079436.png)

[![image-1639641130771.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/psIaRrfHQ9wBZpfw-image-1639641130771.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/psIaRrfHQ9wBZpfw-image-1639641130771.png)

[![image-1639642205256.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/jw5l0WzI5Y8ALfgU-image-1639642205256.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/jw5l0WzI5Y8ALfgU-image-1639642205256.png)

[![image-1639641985534.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/DrNJjO9jkx5xE6PO-image-1639641985534.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/DrNJjO9jkx5xE6PO-image-1639641985534.png)

[![image-1639642001500.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/8noLiSkdjt2fqqH8-image-1639642001500.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/8noLiSkdjt2fqqH8-image-1639642001500.png)

[![image-1639642223899.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/gF1KtsZ7PX1rOcwm-image-1639642223899.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/gF1KtsZ7PX1rOcwm-image-1639642223899.png)

---

### Update &amp; Insert

資料存在則更新，否則新增

[![image-1639642268831.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/U2dkOvazrx7zLp4p-image-1639642268831.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/U2dkOvazrx7zLp4p-image-1639642268831.png)

---

### 返回更新前後的值(updateOne無法返回)

需要返回值的時候使用，不然就只用updateOne

[![image-1639642579954.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/R9Pllrf4SotvLTP4-image-1639642579954.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/R9Pllrf4SotvLTP4-image-1639642579954.png)

# MongoDB教育訓練-06

[![image-1639705033770.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/t1AM4RJGX5ZBtLVf-image-1639705033770.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/t1AM4RJGX5ZBtLVf-image-1639705033770.png)

[![image-1639705137852.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/khGTD0wPcEe6ELd8-image-1639705137852.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/khGTD0wPcEe6ELd8-image-1639705137852.png)

mongoDB 使用Ｂ-tree索引

[![image-1639705482607.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/FahDdBDIM56kjSwr-image-1639705482607.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/FahDdBDIM56kjSwr-image-1639705482607.png)

查詢至少要用到索引，不要full table scan(所以要至少建立可用索引)

[![image-1639705535799.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/IcNsjB8malFbXVe9-image-1639705535799.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/IcNsjB8malFbXVe9-image-1639705535799.png)

[![image-1639705770711.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/3HGEO8hp5SuzV7VI-image-1639705770711.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/3HGEO8hp5SuzV7VI-image-1639705770711.png)

檢查執行計畫快取 -&gt; (無) 有可能優化的索引找出來，同時跑看誰的效率快，加入快取

更新快取時機:

- 當前索引變慢(系統判斷)
- 當新的索引被創建
- server 重啟(memory 清除)

[![image-1639705807311.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/qTqMHpE3PRcGVKSh-image-1639705807311.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/qTqMHpE3PRcGVKSh-image-1639705807311.png)

- 需要指定欄位
- 欄位可以是任何類型

[![image-1639706094542.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/8w46WyVtDVVFOrOX-image-1639706094542.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/8w46WyVtDVVFOrOX-image-1639706094542.png)

[![image-1639706346750.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/zrUfOeVQ03fLN8eh-image-1639706346750.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/zrUfOeVQ03fLN8eh-image-1639706346750.png)

Explain 執行計畫

- queryPlanner : 只顯示執行計畫
- executionStats(最常用): 會把<span style="color: #ff0000;">最優</span>執行計畫執行一遍，並收集相關資訊
- allPlansExecution:把<span style="color: #ff0000;">所有</span>執行計畫執行一遍，並收集相關資訊

[![image-1639706656145.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/UvwFOcKDjW3wYOTv-image-1639706656145.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/UvwFOcKDjW3wYOTv-image-1639706656145.png)

- nReturn: 返回數量
- totalKeyExamined ：找到索引
- totalDocExamined : 查找document數量
- stage:COLLSAN =&gt; 無index
- totalKeyExamined / nReturn 最好小於1000

[![image-1639706909824.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/ANlcZf5NJFiDeENQ-image-1639706909824.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/ANlcZf5NJFiDeENQ-image-1639706909824.png)

number\_of\_reviews: 1 (1: 升冪,-1:降冪)

[![image-1639707147638.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/V7W991262RVWH978-image-1639707147638.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/V7W991262RVWH978-image-1639707147638.png)

通過index 取得資訊：FETCH

[![image-1639707345619.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/qNaUUJCZBvCPfNua-image-1639707345619.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/qNaUUJCZBvCPfNua-image-1639707345619.png)

- nReturn:totalKeyExamined:totalDocExamined 1:1:1 最佳情況

[![image-1639707445597.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/iMAC2vsFjDUcagYR-image-1639707445597.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/iMAC2vsFjDUcagYR-image-1639707445597.png)

[![image-1639707511649.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/b0jijKi7hs3NW8l5-image-1639707511649.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/b0jijKi7hs3NW8l5-image-1639707511649.png)

[![image-1639707565143.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/UPwVfU3YeI8ebflZ-image-1639707565143.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/UPwVfU3YeI8ebflZ-image-1639707565143.png)

取得所有索引

[![image-1639707652623.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/9CLzgoztLs1ULVyh-image-1639707652623.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/9CLzgoztLs1ULVyh-image-1639707652623.png)

刪除索引

[![image-1639707687385.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/Pru2KUGncvB033Iv-image-1639707687385.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/Pru2KUGncvB033Iv-image-1639707687385.png)

[![image-1639707697169.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/azQ4hbFPVFwHFByu-image-1639707697169.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/azQ4hbFPVFwHFByu-image-1639707697169.png)

需要放得下index的記憶體（最低要求）

[![image-1639707720558.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/7GAr6sU8Usp7VSP5-image-1639707720558.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/7GAr6sU8Usp7VSP5-image-1639707720558.png)

db.tablename.stats(檔案除以？(1024 =&gt; KB,102481024 =&gt; MB)).indexSizes

[![image-1639707935930.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/65z7pfIxCgvv4WCQ-image-1639707935930.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/65z7pfIxCgvv4WCQ-image-1639707935930.png)

- storageSize : 物理檔案大小
- 刪除資料不會減少資料庫大小，只是把硬碟位置空出來，重複使用
- 釋放空間：compact [https://docs.mongodb.com/manual/reference/command/compact/](https://docs.mongodb.com/manual/reference/command/compact/)

[![image-1639708000892.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/mUDxLz2Iz2xMZzn2-image-1639708000892.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/mUDxLz2Iz2xMZzn2-image-1639708000892.png)

[![image-1639708353823.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/QLPhvFC28gLvwdnr-image-1639708353823.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/QLPhvFC28gLvwdnr-image-1639708353823.png)

# MongoDB教育訓練-07

- 只能保證分片唯一，不能保證全局唯一
- sparse index : 欄位可能不存在document中使用
- partial index : 依據欄位的值決定要不要放入索引（刪除的不放）

[![image-1639709435352.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/wWPaTmNWtK43dfFl-image-1639709435352.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/wWPaTmNWtK43dfFl-image-1639709435352.png)

- hashed indexes : md5 取前20個byte，並不可作為唯一索引，使用時機：控制索引大小（如url）
- 關於時間不適合，會造成分散在所有分片

[![image-1639709740460.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/BNDz6MjJGH4XQVMy-image-1639709740460.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/BNDz6MjJGH4XQVMy-image-1639709740460.png)

- 索引增加讀的效能，減低寫的效能
- 建議每張表的索引&lt; 10
- 新增資料會做索引異動，更新不會(但更新欄位會)

[![image-1639710154313.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/p9ksssT9foFnXp7U-image-1639710154313.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/p9ksssT9foFnXp7U-image-1639710154313.png)

索引越大，記憶體越大

[![image-1639710316926.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/4zOuCSYrDtGgfnf8-image-1639710316926.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/4zOuCSYrDtGgfnf8-image-1639710316926.png)

### 前綴壓縮

- <span style="color: #ff0000;">App</span> le
- <span style="color: #ff0000;">App</span> le iphon
- <span style="color: #ff0000;">App</span> le store

[![image-1639710384084.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/rrhpi4p8v1TQjucP-image-1639710384084.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/rrhpi4p8v1TQjucP-image-1639710384084.png)

### Multikey indexs

建在array上的index (是不是由系統判斷)

[![image-1639710538365.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/4jvxLsF5Zv6B6JrQ-image-1639710538365.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/4jvxLsF5Zv6B6JrQ-image-1639710538365.png)

[![image-1639710636236.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/xbwrPXNGHcrCzpeC-image-1639710636236.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/xbwrPXNGHcrCzpeC-image-1639710636236.png)

通常使用"comments.rating" : 1 這種索引

[![image-1639710866463.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/kQG5DV39z1YWECuY-image-1639710866463.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/kQG5DV39z1YWECuY-image-1639710866463.png)

[![image-1639711096453.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/EMgtxyUFyCFNrcC0-image-1639711096453.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/EMgtxyUFyCFNrcC0-image-1639711096453.png)

### Compound符合索引

多欄位索引 (欄位A &amp; 欄位B 查詢適用 )

[![image-1639711185981.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/1fStpr6XArU4nsF0-image-1639711185981.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/1fStpr6XArU4nsF0-image-1639711185981.png)

### Compound Indexes 複合索引

- MongoDB 通常使用<span style="color: #ff0000;">一個</span>索引作查詢
- 最多可以做32個
- 順序很重要

[![image-1639711281834.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/bFZZMWa76Rs0PgBT-image-1639711281834.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/bFZZMWa76Rs0PgBT-image-1639711281834.png)

[![image-1639711459539.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/gWtjM19sgBjXu1vs-image-1639711459539.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/gWtjM19sgBjXu1vs-image-1639711459539.png)

欄位順序必須相同

[![image-1639711603003.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/FPft7ie7e0KvO9md-image-1639711603003.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/FPft7ie7e0KvO9md-image-1639711603003.png)

[![image-1639711744724.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/EbJ0MfYoAaSPFIRg-image-1639711744724.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/EbJ0MfYoAaSPFIRg-image-1639711744724.png)

[![image-1639711763345.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/V53vAeoi5saMlNUs-image-1639711763345.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/V53vAeoi5saMlNUs-image-1639711763345.png)

[![image-1639711833183.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/NXkdKdxjZW2po5PQ-image-1639711833183.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/NXkdKdxjZW2po5PQ-image-1639711833183.png)

[![image-1639711867625.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/MiTyDRZgKHj8WhZM-image-1639711867625.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/MiTyDRZgKHj8WhZM-image-1639711867625.png)

[![image-1639711980197.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/PO3Xs0ElcRvrZxDH-image-1639711980197.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/PO3Xs0ElcRvrZxDH-image-1639711980197.png)

[![image-1639712284049.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/omFQETIWY1NDNVfY-image-1639712284049.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/omFQETIWY1NDNVfY-image-1639712284049.png)

[![image-1639712401606.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/PjRvnyiSx4W82DeT-image-1639712401606.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/PjRvnyiSx4W82DeT-image-1639712401606.png)

### ＥＳＲ原則

等值 排序 範圍

10000以內不算數據量大

[![image-1639712527794.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/X2qvWrX7SXv1CsYz-image-1639712527794.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/X2qvWrX7SXv1CsYz-image-1639712527794.png)

兩欄位都是陣列，禁止使用，因為所有數量是陣列的乘積

[![image-1639712629336.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/7gfZXdRRZGTKJXQj-image-1639712629336.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/7gfZXdRRZGTKJXQj-image-1639712629336.png)

###  

#### index 完全覆蓋查詢(資料都從index返回，不用直接查詢document)

\_id 不要出現, multikey 不適用

[![image-1639712718185.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/U5rt5J6oLLv4cPWC-image-1639712718185.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/U5rt5J6oLLv4cPWC-image-1639712718185.png)

[![image-1639712827734.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/QwW6VDciHMXheDrv-image-1639712827734.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/QwW6VDciHMXheDrv-image-1639712827734.png)

```shell
db.listingsAndReviews.find({amenities:"Waterfront","bed_type":{$in:["Futon","Real Bed"]}})
.projection({bedrooms:1, price:1,_id:0,"address.country":1}).explain("executionStats")


db.listingsAndReviews.createIndex({amenities:1, bedrooms:-1,price:11,bed_type:1,first_review:1,last_review:1})
```

# MongoDB教育訓練-08

[![image-1639720047048.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/LnCs1c7WApqvzvv1-image-1639720047048.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/LnCs1c7WApqvzvv1-image-1639720047048.png)

[![image-1639720141995.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/zm9dCzrCLAkNJpCj-image-1639720141995.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/zm9dCzrCLAkNJpCj-image-1639720141995.png)

[![image-1639720365185.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/hyEj4KfssItHj9Aj-image-1639720365185.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/hyEj4KfssItHj9Aj-image-1639720365185.png)

[![image-1639720514211.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/pgvR3xwhVR7f2OIz-image-1639720514211.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/pgvR3xwhVR7f2OIz-image-1639720514211.png)

### Ｗildcard Indexes

- 可以加入所有欄位，但只能命中一個欄位
- 儲存空間的最大
- 不知道要對什麼做搜索的時候適用

[![image-1639720611175.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/uk7pdMVZCU8dEJHi-image-1639720611175.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/uk7pdMVZCU8dEJHi-image-1639720611175.png)

- 使用hints指定索引
- 支援regular (只有前綴固定 ^開頭支援，範圍查詢)
- 可以設定collation (定序)

[![image-1639720820601.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/qngw3DEJxWRlMizI-image-1639720820601.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/qngw3DEJxWRlMizI-image-1639720820601.png)

- 增加一個索引增加10%消耗
- 記憶體最好容納最常需要的索引
- $indexStarts() 可以統計索引的命中率(server 服務以來)
- {a: 1} {a: 1,b:1} 前面這個與後面重複可以刪除

[![image-1639721057745.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/cDmnKgZn3z1Wn1F0-image-1639721057745.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/cDmnKgZn3z1Wn1F0-image-1639721057745.png)

[![image-1639721231592.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/C5FewfIzF4rxbhSq-image-1639721231592.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/C5FewfIzF4rxbhSq-image-1639721231592.png)

### 滾動索引

- 暫停一個結點建立索引，再加入叢集
- 重複兩次後將建好索引主機升為master，下master建索引

[![image-1639721445903.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/Eam5oeYY7MU6DDJ3-image-1639721445903.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/Eam5oeYY7MU6DDJ3-image-1639721445903.png)

index 先隱藏再刪除

[![image-1639721675102.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/Hmqy7FhWbDJgRoft-image-1639721675102.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/Hmqy7FhWbDJgRoft-image-1639721675102.png)

[![image-1639721774327.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/a4ldxj17EbY8rRDq-image-1639721774327.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/a4ldxj17EbY8rRDq-image-1639721774327.png)

# MongoDB教育訓練-09

查詢緩慢

- 沒有使用index

解決

- 查看database log
- 開啟 profiling

[![image-1639721837613.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/VxB6quNoXw59D2qD-image-1639721837613.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/VxB6quNoXw59D2qD-image-1639721837613.png)

[![image-1639721953030.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/eX8GGpQ2wpDf116l-image-1639721953030.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/eX8GGpQ2wpDf116l-image-1639721953030.png)

[![image-1639722038224.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/ufhJqUKSnXZ7SEHc-image-1639722038224.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/ufhJqUKSnXZ7SEHc-image-1639722038224.png)

level 2 性能會影響

[![image-1639722277872.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/QGO3hYQQM0zAyXZ3-image-1639722277872.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/QGO3hYQQM0zAyXZ3-image-1639722277872.png)

db.setProfilingLevel(1,5) #level 1, 查詢大於5毫秒

[![image-1639722367579.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/QI2cwOiCMNzCUrUw-image-1639722367579.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/QI2cwOiCMNzCUrUw-image-1639722367579.png)

---

### 慢查詢的原因

- 缺少index
- 寫入資料超出disk 性能
- 高CPU使用
- 巢狀join

[![image-1639722562542.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/0lef2dE3IDxIl9Fm-image-1639722562542.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/0lef2dE3IDxIl9Fm-image-1639722562542.png)

---

[![image-1639723682887.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/UC4AiylyoipWvR8X-image-1639723682887.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/UC4AiylyoipWvR8X-image-1639723682887.png)

[![image-1639723703346.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/Qr9kRrZQbcNhThL5-image-1639723703346.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/Qr9kRrZQbcNhThL5-image-1639723703346.png)

[![image-1639723975695.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/H3NOG4rJDGcaqBlb-image-1639723975695.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/H3NOG4rJDGcaqBlb-image-1639723975695.png)

[![image-1639724124142.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/ybJiSLF4TbQmBTzK-image-1639724124142.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/ybJiSLF4TbQmBTzK-image-1639724124142.png)

[![image-1639723989853.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/Yqt1hEruRAL7S0xe-image-1639723989853.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/Yqt1hEruRAL7S0xe-image-1639723989853.png)

[![image-1639724147258.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/ejdOvHO91KXtbi4P-image-1639724147258.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/ejdOvHO91KXtbi4P-image-1639724147258.png)

[![image-1639724475480.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/5zO0nzrtHpYPxbMo-image-1639724475480.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/5zO0nzrtHpYPxbMo-image-1639724475480.png)

[MongoDB Compass Download | MongoDB](https://www.mongodb.com/try/download/compass?tck=docs_compass)

[![image-1639724538158.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/ce4cJzb96maJr7T6-image-1639724538158.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/ce4cJzb96maJr7T6-image-1639724538158.png)

[![image-1639724837767.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/7lGIDr1EPdAigO6W-image-1639724837767.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/7lGIDr1EPdAigO6W-image-1639724837767.png)

[![image-1639725028397.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/XLwTmGbYiHbsTS7i-image-1639725028397.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/XLwTmGbYiHbsTS7i-image-1639725028397.png)

[![image-1639725068655.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/hEnDhowWWilE3kL4-image-1639725068655.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/hEnDhowWWilE3kL4-image-1639725068655.png)

[![image-1639725233859.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/DhOg54QUXLFGGxDZ-image-1639725233859.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/DhOg54QUXLFGGxDZ-image-1639725233859.png)

[![image-1639725262005.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/g1j8D8AgfsVG7tgj-image-1639725262005.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/g1j8D8AgfsVG7tgj-image-1639725262005.png)

[![image-1639725411915.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/vmxyYKJqoxaybrWB-image-1639725411915.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/vmxyYKJqoxaybrWB-image-1639725411915.png)

[![image-1639726437813.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/cxzxIMca9fhL52lF-image-1639726437813.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/cxzxIMca9fhL52lF-image-1639726437813.png)

[![image-1639726650401.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/Jmp44ML3bOBXP0O5-image-1639726650401.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/Jmp44ML3bOBXP0O5-image-1639726650401.png)

[![image-1639726779203.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/1TEZigJ62aqgiaud-image-1639726779203.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/1TEZigJ62aqgiaud-image-1639726779203.png)

[![image-1639727675464.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/H4GJTeQ67U3GZcEW-image-1639727675464.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/H4GJTeQ67U3GZcEW-image-1639727675464.png)

[![image-1639727685022.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/GnGQRtLft0j6ZNNU-image-1639727685022.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/GnGQRtLft0j6ZNNU-image-1639727685022.png)

[![image-1639727814455.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/kdGOflVBf1uVssfs-image-1639727814455.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/kdGOflVBf1uVssfs-image-1639727814455.png)

[![image-1639727848116.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/IJN81R5wMLmxxQ0m-image-1639727848116.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/IJN81R5wMLmxxQ0m-image-1639727848116.png)

[![image-1639729086371.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/ULXwTEx0uINo8kEN-image-1639729086371.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/ULXwTEx0uINo8kEN-image-1639729086371.png)

[![image-1639728235333.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/QyhN1K9QMu771Exp-image-1639728235333.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/QyhN1K9QMu771Exp-image-1639728235333.png)

# MongoDB教育訓練-20211223-01

Hideden Replica Set Member 隱藏結點，不太使用（歷史，所有請求不會訪問）

Delayed Replica Set Member 不太使用，先將資料取回不同步(延遲一段時間，保持舊資料，備援用)

[![image-1640224635457.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/XOGkmJFnD85OvMkq-image-1640224635457.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/XOGkmJFnD85OvMkq-image-1640224635457.png)

[![image-1640229898504.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/W9QymtxKIAMTIkvu-image-1640229898504.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/W9QymtxKIAMTIkvu-image-1640229898504.png)

- 如何知道主結點？ Driver 告訴你
- Driver 幫你追蹤網路架構，不需知道主從節點
- 為截點點打上標籤，通過driver，告訴你

[![image-1640225138949.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/mkaD8FGMUONjNWvt-image-1640225138949.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/mkaD8FGMUONjNWvt-image-1640225138949.png)

Driver 版本匹配

[![image-1640225409514.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/XUV6UBCZHoCy7WiL-image-1640225409514.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/XUV6UBCZHoCy7WiL-image-1640225409514.png)

[![image-1640225581875.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/ljScMKeKsnerrfZT-image-1640225581875.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/ljScMKeKsnerrfZT-image-1640225581875.png)

[![image-1640225640585.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/kjE3XuBBGE2pWXg7-image-1640225640585.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/kjE3XuBBGE2pWXg7-image-1640225640585.png)

[![image-1640225672880.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/K36gksltsXNU7Sp1-image-1640225672880.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/K36gksltsXNU7Sp1-image-1640225672880.png)

Oplog 會紀錄所有變更，增刪改查

[![image-1640225826529.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/VkuI6ZzjAZYi0Qy0-image-1640225826529.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/VkuI6ZzjAZYi0Qy0-image-1640225826529.png)

[![image-1640226097101.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/uoM1F32LgN72bL9r-image-1640226097101.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/uoM1F32LgN72bL9r-image-1640226097101.png)

[![image-1640226197262.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/MXHz4iiUS9dvzxIq-image-1640226197262.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/MXHz4iiUS9dvzxIq-image-1640226197262.png)

[![image-1640226289283.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/rFNNUT6zOP9kDniK-image-1640226289283.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/rFNNUT6zOP9kDniK-image-1640226289283.png)

節點從最近節點複製

[![image-1640226546216.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/xt0a7FrsUW8oVGJh-image-1640226546216.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/xt0a7FrsUW8oVGJh-image-1640226546216.png)

我們可以直接連線至從節點

[![image-1640226730042.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/6U4q1S3mIsM220jG-image-1640226730042.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/6U4q1S3mIsM220jG-image-1640226730042.png)

[![image-1640226806230.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/ukiGN9JWAOhYyvMD-image-1640226806230.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/ukiGN9JWAOhYyvMD-image-1640226806230.png)

[![image-1640226630457.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/AVf1EvlPoUtMYUx5-image-1640226630457.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/AVf1EvlPoUtMYUx5-image-1640226630457.png)

從節點查詢資料會出現告警，需下以下指令

db.getMongo().setReadPref("secondaryPreferred")

[![image-1640227049033.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/RBSiuFMGfU7k2rjQ-image-1640227049033.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/RBSiuFMGfU7k2rjQ-image-1640227049033.png)

[![image-1640227181462.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/UJowcNEosUMKYFZI-image-1640227181462.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/UJowcNEosUMKYFZI-image-1640227181462.png)

[![image-1640228152411.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/leh8xE0qxe8pz8C6-image-1640228152411.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/leh8xE0qxe8pz8C6-image-1640228152411.png)

[![image-1640228847213.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/CdrXbc5ZKv9PmB5a-image-1640228847213.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/CdrXbc5ZKv9PmB5a-image-1640228847213.png)

[![image-1640229625178.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/joQKDZO1k9gti5tX-image-1640229625178.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/joQKDZO1k9gti5tX-image-1640229625178.png)

[![image-1640229774613.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/Te9XUXyWYniIPvDR-image-1640229774613.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/Te9XUXyWYniIPvDR-image-1640229774613.png)

[![image-1640229875125.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/LnCVMxTrZg0zSxNT-image-1640229875125.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/LnCVMxTrZg0zSxNT-image-1640229875125.png)

總節點是奇數個才能選出主節點，剩下最新的會變成主節點

[![image-1640229994266.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/01gyMSjgcs4D3EFd-image-1640229994266.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/01gyMSjgcs4D3EFd-image-1640229994266.png)

網路斷開主節點還是存在

[![image-1640230063320.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/otVqDPjheDK2ZNF7-image-1640230063320.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/otVqDPjheDK2ZNF7-image-1640230063320.png)

需要有大多數才能投票，此主節點失效

[![image-1640230096608.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/GWTdbxzxI27wdAxk-image-1640230096608.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/GWTdbxzxI27wdAxk-image-1640230096608.png)

會可以投票主節點，DC1有機會成為主節點(資料較新？)

[![image-1640230277213.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/Wlcc8tGbUqUerXZc-image-1640230277213.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/Wlcc8tGbUqUerXZc-image-1640230277213.png)

[![image-1640230905006.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/m9u6CRTUvwLEjSbz-image-1640230905006.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/m9u6CRTUvwLEjSbz-image-1640230905006.png)

# MongoDB教育訓練-20211223-02

- 資料先寫入記憶體 ，
- checkpoint =&gt; 一分鐘( or 資料到達大小) 髒數據寫入DB
- journal log 60ms 寫入 db
- db 最多錯失60 ms 資料
- "OK - committed" =&gt; 寫到哪裡？ write concerns 決定
- 資料遺失的時候？我會知曉？
- knowingly log =&gt; 已知的寫入失敗
- unknowingly log =&gt; 未知的寫入失敗

[![image-1640237795605.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/51ki1KDJEyWkz1kV-image-1640237795605.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/51ki1KDJEyWkz1kV-image-1640237795605.png)

[![image-1640238366642.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/48LANg6oh5A8V3c4-image-1640238366642.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/48LANg6oh5A8V3c4-image-1640238366642.png)

[![image-1640238415549.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/LLjDAcWlqBdgN3Vs-image-1640238415549.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/LLjDAcWlqBdgN3Vs-image-1640238415549.png)

[![image-1640238497608.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/mamGXqUk0sI8jwSt-image-1640238497608.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/mamGXqUk0sI8jwSt-image-1640238497608.png)

當資訊不同步，server 必須 rollback 確保資訊一致 =&gt; { x: 100} (移除)

[![image-1640238562747.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/0mRZdFeDnlJE5hoo-image-1640238562747.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/0mRZdFeDnlJE5hoo-image-1640238562747.png)

[![image-1640238622753.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/BZdfu2R1oiKJs0dI-image-1640238622753.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/BZdfu2R1oiKJs0dI-image-1640238622753.png)

The case for Majority writes

in the previeous scenario:

date is writtten to the primary

primary acknowledges write to application

primary dies before secondary reads data

必須所以資料到達大多數節點(majoruty commit point)，才能稱之為持久化[![image-1640238761360.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/ztBPvEq8CD8CwrRx-image-1640238761360.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/ztBPvEq8CD8CwrRx-image-1640238761360.png)

[![image-1640238911776.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/bHKAnFuE7H5KNaY8-image-1640238911776.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/bHKAnFuE7H5KNaY8-image-1640238911776.png)

### 寫的關注度Write Concern

- OK, commitresd 意義
- (w: 到達多少個節點)(j: 如何才算寫成功 0:記憶體, 1:硬碟)
- w:0 不推薦
- w:1, j:1
- w: "majority" (j: 1)
- w:3 無意義(假設叢集有3節點，萬一1節點丟失，無法滿足)

[![image-1640239010111.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/UKzSJDUyJPnSJ7I8-image-1640239010111.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/UKzSJDUyJPnSJ7I8-image-1640239010111.png)

```shell
var wc = { w: 0 }
totalrecs = 10000
batchsize = 1
nbatches = totalrecs / batchsize
var start = new Date()
for(x=0;x<nbatches;x++) {
	recs = []
	for(y=0;y<batchsize;y++) {recs.push({a:1,b:"hello"})}
	db.test.insertMany(recs,{writeConcern: wc})
}

var end=new Date()
print(`${end-start} milliseconds`)
```

[![image-1640239636788.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/D2tSUBqCfLK9n7Dt-image-1640239636788.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/D2tSUBqCfLK9n7Dt-image-1640239636788.png)

### 讀的關注度 Read Concerns 

RDBS 隔離等級概念

Read local 主節點損毀才是髒讀

Read majority 讀舊一點的資料

Read snapshot :

Read Linearizable: 讀取時會確認資料是否到達大多數節點

[![image-1640240583240.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/iJFj4Cs5o6NUaKKk-image-1640240583240.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/iJFj4Cs5o6NUaKKk-image-1640240583240.png)

[![image-1640239853753.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/2kPdvY1LGbZGS2Ae-image-1640239853753.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/2kPdvY1LGbZGS2Ae-image-1640239853753.png)

### 讀取資料會從哪個節點讀？

- Read from nearest Geographically (從最近的節點 ping 值，最小的)
- Read from a specific set of servers (設定標記好的 tag server )

[![image-1640240657381.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/FaHVQBXp6O5EAO0M-image-1640240657381.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/FaHVQBXp6O5EAO0M-image-1640240657381.png)

### <span style="color: #ff0000;">非必要不使用</span>

[![image-1640240968077.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/7GlKL03YZFWX7du3-image-1640240968077.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/7GlKL03YZFWX7du3-image-1640240968077.png)

[![image-1640241039739.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/BjmFLZk7hGaHyjg9-image-1640241039739.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/BjmFLZk7hGaHyjg9-image-1640241039739.png)

# MongoDB教育訓練-20211223-03

vertical scaling 垂直擴展(升級到某個程度，會很貴並有極限)

horizontal sacling 水平擴展 (比較省成本)

[![image-1640242289465.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/WNPaHJCxNDV7y5ek-image-1640242289465.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/WNPaHJCxNDV7y5ek-image-1640242289465.png)

沒有一定要shard(如果資料不多不需要，管理複雜度高)

[![image-1640242412103.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/9wbUHEyWdAtGiHWa-image-1640242412103.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/9wbUHEyWdAtGiHWa-image-1640242412103.png)

[![image-1640242564943.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/k4B2vYQF9vFVLMHI-image-1640242564943.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/k4B2vYQF9vFVLMHI-image-1640242564943.png)

- why learn about sharding now?
- you might not need to shard for years
- but decisions you make about schema design befoe that matter
- start planning for sharding on day one

[![image-1640242581364.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/lwlqelUGcUlyNd54-image-1640242581364.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/lwlqelUGcUlyNd54-image-1640242581364.png)

[![image-1640242682522.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/nbXWY4DLAOypwauJ-image-1640242682522.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/nbXWY4DLAOypwauJ-image-1640242682522.png)

[![image-1640242841645.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/WA1BuRXuhph6yNSj-image-1640242841645.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/WA1BuRXuhph6yNSj-image-1640242841645.png)

mongos 屏蔽所有細節

[![image-1640242859417.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/4pjQ98bgVwvRhQhI-image-1640242859417.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/4pjQ98bgVwvRhQhI-image-1640242859417.png)

configuration server 決定資料分布

[![image-1640242920382.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/8vpdhQPhwezSA16S-image-1640242920382.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/8vpdhQPhwezSA16S-image-1640242920382.png)

[![image-1640242991219.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/udIFf8TK6WQ86Vat-image-1640242991219.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/udIFf8TK6WQ86Vat-image-1640242991219.png)

[![image-1640243006562.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/TciPkWgEPCb2dWeW-image-1640243006562.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/TciPkWgEPCb2dWeW-image-1640243006562.png)

[![image-1640243117106.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/eJGjPxByWF0mq62G-image-1640243117106.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/eJGjPxByWF0mq62G-image-1640243117106.png)

修改分片鍵

[![image-1640243247361.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/9t1Rsl2zJwqR6fvs-image-1640243247361.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/9t1Rsl2zJwqR6fvs-image-1640243247361.png)

選擇分片鍵原則

shard keys 不一定是要一樣的，重複的不可超過64MB

[![image-1640243397277.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/yJvQFYh4JP9ch7gx-image-1640243397277.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/yJvQFYh4JP9ch7gx-image-1640243397277.png)

[![image-1640243910637.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/pXt9gNUTgm7f9JOY-image-1640243910637.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/pXt9gNUTgm7f9JOY-image-1640243910637.png)

[![image-1640243925444.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/9ORq0VI5ydsIsIEs-image-1640243925444.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/9ORq0VI5ydsIsIEs-image-1640243925444.png)

[![image-1640244070167.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/ti8OKD2bA7Om04oj-image-1640244070167.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/ti8OKD2bA7Om04oj-image-1640244070167.png)

[![image-1640244228199.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/RGrDhrhpMgfLQqdY-image-1640244228199.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/RGrDhrhpMgfLQqdY-image-1640244228199.png)

[![image-1640244431054.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/m3MrqhOquZ4HBcS1-image-1640244431054.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/m3MrqhOquZ4HBcS1-image-1640244431054.png)

[![image-1640245827455.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/EvsLc9SiqIkkdFmv-image-1640245827455.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/EvsLc9SiqIkkdFmv-image-1640245827455.png)

[![image-1640246122904.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/aF9XYZxnA83gyNy7-image-1640246122904.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/aF9XYZxnA83gyNy7-image-1640246122904.png)

[![image-1640246580335.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/3vkpAb7OMpoNmKs1-image-1640246580335.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/3vkpAb7OMpoNmKs1-image-1640246580335.png)

[![image-1640246143964.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/6bGqPLLgJrVFwyv6-image-1640246143964.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/6bGqPLLgJrVFwyv6-image-1640246143964.png)

[![image-1640246907569.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/6n07LgrqZTiv1aXi-image-1640246907569.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/6n07LgrqZTiv1aXi-image-1640246907569.png)

#### 預分片

[![image-1640247049023.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/1XBWLTnq72Gwwj38-image-1640247049023.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/1XBWLTnq72Gwwj38-image-1640247049023.png)

範例 1000億筆資料

[![image-1640247152836.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/FJ9Mgvm0w06JRLp6-image-1640247152836.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/FJ9Mgvm0w06JRLp6-image-1640247152836.png)

ＹＣＳＢ壓測工具

[![image-1640247396813.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/MX2RzMWGwFipMVK8-image-1640247396813.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/MX2RzMWGwFipMVK8-image-1640247396813.png)

[![image-1640247645818.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/I4xvjQ0YY6zvvmfU-image-1640247645818.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/I4xvjQ0YY6zvvmfU-image-1640247645818.png)

[![image-1640247664050.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/8byDycHQAHcmPwcA-image-1640247664050.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/8byDycHQAHcmPwcA-image-1640247664050.png)

[![image-1640247809556.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/4Y3SYEpqiGFJixD1-image-1640247809556.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/4Y3SYEpqiGFJixD1-image-1640247809556.png)

primary shared 最忙

[![image-1640247884753.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/7PxUBnmsctBGBQMm-image-1640247884753.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/7PxUBnmsctBGBQMm-image-1640247884753.png)

完全停止

[![image-1640248229678.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/2fxzL3fhexEFquxI-image-1640248229678.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/2fxzL3fhexEFquxI-image-1640248229678.png)

[![image-1640248345321.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/vLTpEPOwoTqLepl9-image-1640248345321.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/vLTpEPOwoTqLepl9-image-1640248345321.png)

分散多筆sharding ，利用換取ＣＰＵ計算

[![image-1640248651952.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/PlArjNlZLfbdB1gX-image-1640248651952.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/PlArjNlZLfbdB1gX-image-1640248651952.png)

# MongoDB教育訓練-20211223-04-關於安全

[![image-1640248761008.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/ySQYEekObyv3QadV-image-1640248761008.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/ySQYEekObyv3QadV-image-1640248761008.png)

#### 對稱加密 &amp;&amp; 非對稱加密

[![image-1640248839685.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/Q8XvluDNeNNpytsW-image-1640248839685.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/Q8XvluDNeNNpytsW-image-1640248839685.png)

[![image-1640248938126.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/It7SCKEtnipwfTr0-image-1640248938126.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/It7SCKEtnipwfTr0-image-1640248938126.png)

[![image-1640248975046.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/osit9OM9JSbWq2MX-image-1640248975046.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/osit9OM9JSbWq2MX-image-1640248975046.png)

[![image-1640249034932.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/JacBAaOpvBvfiSV7-image-1640249034932.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/JacBAaOpvBvfiSV7-image-1640249034932.png)

[![image-1640249067545.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/id9uXrE6WW9cuzzT-image-1640249067545.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/id9uXrE6WW9cuzzT-image-1640249067545.png)

[![image-1640249240963.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/NfI2UR7LAaHd9367-image-1640249240963.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/NfI2UR7LAaHd9367-image-1640249240963.png)

[![image-1640249310207.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/bkl9fBQIVdO3xUNP-image-1640249310207.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/bkl9fBQIVdO3xUNP-image-1640249310207.png)

[![image-1640249547717.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/MjKSfKiciXWufhpe-image-1640249547717.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/MjKSfKiciXWufhpe-image-1640249547717.png)

[![image-1640249574633.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/Dv7NW6q2ic340V2v-image-1640249574633.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/Dv7NW6q2ic340V2v-image-1640249574633.png)

[![image-1640249605046.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/Ts7kmUxz4kLb0jv4-image-1640249605046.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/Ts7kmUxz4kLb0jv4-image-1640249605046.png)

[![image-1640249683383.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/pcAor5Heq8Gj4Uzh-image-1640249683383.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/pcAor5Heq8Gj4Uzh-image-1640249683383.png)

[![image-1640249745767.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/vk2opfSRIIjBFhuA-image-1640249745767.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/vk2opfSRIIjBFhuA-image-1640249745767.png)

[![image-1640249753776.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/KKcHEp38w17qGvd4-image-1640249753776.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/KKcHEp38w17qGvd4-image-1640249753776.png)

[![image-1640249776866.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/x2lJM33ncrFjEyGp-image-1640249776866.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/x2lJM33ncrFjEyGp-image-1640249776866.png)

[![image-1640249808115.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/nqJk2PVnPyCt5goV-image-1640249808115.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/nqJk2PVnPyCt5goV-image-1640249808115.png)

[![image-1640249932583.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/XbsiFAvbAgKPTXoG-image-1640249932583.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/XbsiFAvbAgKPTXoG-image-1640249932583.png)

[![image-1640249992603.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/VU4U1bSs9zQrp5TO-image-1640249992603.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/VU4U1bSs9zQrp5TO-image-1640249992603.png)

[![image-1640250016804.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/gyGSRS3eNoKiMP9G-image-1640250016804.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/gyGSRS3eNoKiMP9G-image-1640250016804.png)

[![image-1640250154038.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/ob2lCOzhtWWRoUv2-image-1640250154038.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/ob2lCOzhtWWRoUv2-image-1640250154038.png)

[![image-1640250302044.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/dOnK0REsGmhhsako-image-1640250302044.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/dOnK0REsGmhhsako-image-1640250302044.png)

[![image-1640250446209.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/sWIVjFXtfE1HBFgb-image-1640250446209.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/sWIVjFXtfE1HBFgb-image-1640250446209.png)

[![image-1640250681849.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/8LHaIdePG0IXgFMb-image-1640250681849.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/8LHaIdePG0IXgFMb-image-1640250681849.png)

[![image-1640250846041.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/jzeUQLcn6iJrCA32-image-1640250846041.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/jzeUQLcn6iJrCA32-image-1640250846041.png)

# MySQL

# 【MySQL】 mysqldump參數大全

###  

[IT 研究室 ( 前IT DBA's 資訊站): \[轉載\] MySQL mysqldump參數大全 (jaychu649.blogspot.com)](https://jaychu649.blogspot.com/2018/05/mysql-mysqldump.html)

要注意的是--master-data、--dump-slave兩個參數的差別, 以前從未注意過--dump-slave參數

\--master-data用於在master端dump資料，用於建立slave  
\--dump-slave使用者在slave端dump資料，建立新的slave,至少是第2個slave,也就是已經有A--&gt;B,現在從B上匯出資料建立A--&gt;C的複製

**<span lang="EN-US">--all-databases , -A</span>**<span lang="EN-US">  
</span>匯出全部資料庫。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases</span>`<span lang="EN-US">  
**--all-tablespaces , -Y**  
</span>匯出全部表空間。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --all-tablespaces</span>`<span lang="EN-US">  
**--no-tablespaces , -y**  
</span>不匯出任何表空間資訊。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --no-tablespaces</span>`<span lang="EN-US">  
**--add-drop-database**  
</span>每個資料庫創建之前添加<span lang="EN-US">drop</span>資料庫語句。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --add-drop-database</span>`<span lang="EN-US">  
**--add-drop-table**  
</span>每個資料表創建之前添加<span lang="EN-US">drop</span>資料表語句。<span lang="EN-US">(</span>預設為打開狀態，使用<span lang="EN-US">--skip-add-drop-table</span>取消選項<span lang="EN-US">)</span><span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases (</span>``預設添加``<span lang="EN-US">drop</span>``語句``<span lang="EN-US">)</span>`<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases –skip-add-drop-table (</span>``取消``<span lang="EN-US">drop</span>``語句``<span lang="EN-US">)</span>`<span lang="EN-US">  
**--add-locks**  
</span>在每個表匯出之前增加<span lang="EN-US">LOCK TABLES</span>並且之後<span lang="EN-US">UNLOCK TABLE</span>。<span lang="EN-US">(</span>預設為打開狀態，使用<span lang="EN-US">--skip-add-locks</span>取消選項<span lang="EN-US">)</span><span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases (</span>``預設添加``<span lang="EN-US">LOCK</span>``語句``<span lang="EN-US">)</span>`<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases –skip-add-locks (</span>``取消``<span lang="EN-US">LOCK</span>``語句``<span lang="EN-US">)</span>`<span lang="EN-US">  
**--allow-keywords**  
</span>允許創建是關鍵字的列名字。這由表名首碼於每個列名做到。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --allow-keywords</span>`<span lang="EN-US">  
**--apply-slave-statements**  
</span>在<span lang="EN-US">'CHANGE MASTER'</span>前添加<span lang="EN-US">'STOP SLAVE'</span>，並且在匯出的最後添加<span lang="EN-US">'START SLAVE'</span>。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --apply-slave-statements</span>`<span lang="EN-US">  
**--character-sets-dir**  
</span>字元集檔的目錄<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --character-sets-dir=/usr/local/mysql/share/mysql/charsets</span>`<span lang="EN-US">  
**--comments**  
</span>附加注釋資訊。默認為打開，可以用<span lang="EN-US">--skip-comments</span>取消<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases (</span>``預設記錄注釋``<span lang="EN-US">)</span>`<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --skip-comments (</span>``取消注釋``<span lang="EN-US">)</span>`<span lang="EN-US">  
**--compatible**  
</span>匯出的資料將和其它資料庫或舊版本的<span lang="EN-US">MySQL </span>相相容。值可以為<span lang="EN-US">ansi</span>、<span lang="EN-US">mysql323</span>、<span lang="EN-US">mysql40</span>、<span lang="EN-US">postgresql</span>、<span lang="EN-US">oracle</span>、<span lang="EN-US">mssql</span>、<span lang="EN-US">db2</span>、<span lang="EN-US">maxdb</span>、<span lang="EN-US">no\_key\_options</span>、<span lang="EN-US">no\_tables\_options</span>、<span lang="EN-US">no\_field\_options</span>等，<span lang="EN-US">  
</span>要使用幾個值，用逗號將它們隔開。它並不保證能完全相容，而是儘量相容。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --compatible=ansi</span>`<span lang="EN-US">  
**--compact**  
</span>匯出更少的輸出資訊<span lang="EN-US">(</span>用於調試<span lang="EN-US">)</span>。去掉注釋和頭尾等結構。可以使用選項：<span lang="EN-US">--skip-add-drop-table --skip-add-locks --skip-comments --skip-disable-keys</span><span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --compact</span>`<span lang="EN-US">  
**--complete-insert, -c**  
</span>使用完整的<span lang="EN-US">insert</span>語句<span lang="EN-US">(</span>包含列名稱<span lang="EN-US">)</span>。這麼做能提高插入效率，但是可能會受到<span lang="EN-US">max\_allowed\_packet</span>參數的影響而導致插入失敗。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --complete-insert</span>`<span lang="EN-US">  
**--compress, -C**  
</span>在用戶端和伺服器之間啟用壓縮傳遞所有資訊<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --compress</span>`<span lang="EN-US">  
**--create-options, -a**  
</span>在<span lang="EN-US">CREATE TABLE</span>語句中包括所有<span lang="EN-US">MySQL</span>特性選項。<span lang="EN-US">(</span>預設為打開狀態<span lang="EN-US">)</span><span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases</span>`<span lang="EN-US">  
**--databases, -B**  
</span>匯出幾個資料庫。參數後面所有名字參量都被看作資料庫名。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --databases test mysql</span>`<span lang="EN-US">  
**--debug**  
</span>輸出<span lang="EN-US">debug</span>資訊，用於調試。預設值為：<span lang="EN-US">d:t:o,/tmp/mysqldump.trace</span><span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --debug</span>`<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --debug="d:t:o,/tmp/debug.trace"</span>`<span lang="EN-US">  
**--debug-check**  
</span>檢查記憶體和打開檔使用說明並退出。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --debug-check</span>`<span lang="EN-US">  
**--debug-info**  
</span>輸出調試資訊並退出<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --debug-info</span>`<span lang="EN-US">  
**--default-character-set**  
</span>設置預設字元集，預設值為<span lang="EN-US">utf8</span><span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --default-character-set=latin1</span>`<span lang="EN-US">  
**--delayed-insert**  
</span>採用延時插入方式（<span lang="EN-US">INSERT DELAYED</span>）匯出資料<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --delayed-insert</span>`<span lang="EN-US">  
**--delete-master-logs**  
master</span>備份後刪除日誌<span lang="EN-US">. </span>這個參數將自動啟動<span lang="EN-US">--master-data</span>。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --delete-master-logs</span>`<span lang="EN-US">  
**--disable-keys**  
</span>對於每個表，用<span lang="EN-US">/*!40000 ALTER TABLE tbl\_name DISABLE KEYS \*/;*</span>*和<span lang="EN-US">/</span>*<span lang="EN-US">!40000 ALTER TABLE tbl\_name ENABLE KEYS \*/;</span>語句引用<span lang="EN-US">INSERT</span>語句。這樣可以更快地導入<span lang="EN-US">dump</span>出來的檔，因為它是在插入所有行後創建索引的。該選項只適合<span lang="EN-US">MyISAM</span>表，預設為打開狀態。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases</span>`<span lang="EN-US">  
**--dump-slave**  
</span>該選項將導致主的<span lang="EN-US">binlog</span>位置和檔案名追加到匯出資料的檔中。設置為<span lang="EN-US">1</span>時，將會以<span lang="EN-US">CHANGE MASTER</span>命令輸出到資料檔案；設置為<span lang="EN-US">2</span>時，在命令前增加說明資訊。該選項將會打開<span lang="EN-US">--lock-all-tables</span>，除非<span lang="EN-US">--single-transaction</span>被指定。該選項會自動關閉<span lang="EN-US">--lock-tables</span>選項。預設值為<span lang="EN-US">0</span>。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --dump-slave=1</span>`<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --dump-slave=2</span>`<span lang="EN-US">  
**--events, -E**  
</span>匯出事件。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --events</span>`<span lang="EN-US">  
**--extended-insert, -e**  
</span>使用具有多個<span lang="EN-US">VALUES</span>列的<span lang="EN-US">INSERT</span>語法。這樣使匯出檔更小，並加速導入時的速度。預設為打開狀態，使用<span lang="EN-US">--skip-extended-insert</span>取消選項。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases</span>`<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases--skip-extended-insert (</span>``取消選項``<span lang="EN-US">)</span>`<span lang="EN-US">  
**--fields-terminated-by**  
</span>匯出檔中忽略給定欄位。與<span lang="EN-US">--tab</span>選項一起使用，不能用於<span lang="EN-US">--databases</span>和<span lang="EN-US">--all-databases</span>選項<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p test test --tab="/home/mysql" --fields-terminated-by="#"</span>`<span lang="EN-US">  
**--fields-enclosed-by**  
</span>輸出檔中的各個欄位用給定字元包裹。與<span lang="EN-US">--tab</span>選項一起使用，不能用於<span lang="EN-US">--databases</span>和<span lang="EN-US">--all-databases</span>選項<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p test test --tab="/home/mysql" --fields-enclosed-by="#"</span>`<span lang="EN-US">  
**--fields-optionally-enclosed-by**  
</span>輸出檔中的各個欄位用給定字元選擇性包裹。與<span lang="EN-US">--tab</span>選項一起使用，不能用於<span lang="EN-US">--databases</span>和<span lang="EN-US">--all-databases</span>選項<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p test test --tab="/home/mysql" --fields-enclosed-by="#" --fields-optionally-enclosed-by ="#"</span>`<span lang="EN-US">  
**--fields-escaped-by**  
</span>輸出檔中的各個欄位忽略給定字元。與<span lang="EN-US">--tab</span>選項一起使用，不能用於<span lang="EN-US">--databases</span>和<span lang="EN-US">--all-databases</span>選項<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p mysql user --tab="/home/mysql" --fields-escaped-by="#"</span>`<span lang="EN-US">  
**--flush-logs**  
</span>開始匯出之前刷新日誌。<span lang="EN-US">  
</span>請注意：假如一次匯出多個資料庫<span lang="EN-US">(</span>使用選項<span lang="EN-US">--databases</span>或者<span lang="EN-US">--all-databases)</span>，將會逐個資料庫刷新日誌。除使用<span lang="EN-US">--lock-all-tables</span>或者<span lang="EN-US">--master-data</span>外。在這種情況下，日誌將會被刷新一次，相應的所以表同時被鎖定。因此，如果打算同時匯出和刷新日誌應該使用<span lang="EN-US">--lock-all-tables </span>或者<span lang="EN-US">--master-data </span>和<span lang="EN-US">--flush-logs</span>。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --flush-logs</span>`<span lang="EN-US">  
**--flush-privileges**  
</span>在匯出<span lang="EN-US">mysql</span>資料庫之後，發出一條<span lang="EN-US">FLUSH PRIVILEGES </span>語句。為了正確恢復，該選項應該用於匯出<span lang="EN-US">mysql</span>資料庫和依賴<span lang="EN-US">mysql</span>資料庫資料的任何時候。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --flush-privileges</span>`<span lang="EN-US">  
**--force**  
</span>在匯出過程中忽略出現的<span lang="EN-US">SQL</span>錯誤。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --force</span>`<span lang="EN-US">  
**--help**  
</span>顯示説明資訊並退出。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump --help</span>`<span lang="EN-US">  
**--hex-blob**  
</span>使用十六進位格式匯出二進位字元串欄位。如果有二進位資料就必須使用該選項。影響到的欄位類型有<span lang="EN-US">BINARY</span>、<span lang="EN-US">VARBINARY</span>、<span lang="EN-US">BLOB</span>。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --all-databases --hex-blob</span>`<span lang="EN-US">  
**--host, -h**  
</span>需要匯出的主機資訊<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases</span>`<span lang="EN-US">  
**--ignore-table**  
</span>不匯出指定表。指定忽略多個表時，需要重複多次，每次一個表。每個表必須同時指定資料庫和表名。例如：<span lang="EN-US">--ignore-table=database.table1 --ignore-table=database.table2 ……</span><span lang="EN-US">  
mysqldump -uroot -p --host=localhost --all-databases --ignore-table=mysql.user  
**--include-master-host-port**  
</span>在<span lang="EN-US">--dump-slave</span>產生的<span lang="EN-US">'CHANGE MASTER TO..'</span>語句中增加<span lang="EN-US">'MASTER\_HOST=</span>，<span lang="EN-US">MASTER\_PORT='  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --include-master-host-port</span>`<span lang="EN-US">  
**--insert-ignore**  
</span>在插入行時使用<span lang="EN-US">INSERT IGNORE</span>語句<span lang="EN-US">.  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --insert-ignore</span>`<span lang="EN-US">  
**--lines-terminated-by**  
</span>輸出檔的每行用給定字串劃分。與<span lang="EN-US">--tab</span>選項一起使用，不能用於<span lang="EN-US">--databases</span>和<span lang="EN-US">--all-databases</span>選項。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost test test --tab="/tmp/mysql" --lines-terminated-by="##"</span>`<span lang="EN-US">  
**--lock-all-tables, -x**  
</span>提交請求鎖定所有資料庫中的所有表，以保證資料的一致性。這是一個全域讀鎖，並且自動關閉<span lang="EN-US">--single-transaction </span>和<span lang="EN-US">--lock-tables </span>選項。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --lock-all-tables</span>`<span lang="EN-US">  
**--lock-tables, -l**  
</span>開始匯出前，鎖定所有表。用<span lang="EN-US">READ LOCAL</span>鎖定表以允許<span lang="EN-US">MyISAM</span>表並行插入。對於支援事務的表例如<span lang="EN-US">InnoDB</span>和<span lang="EN-US">BDB</span>，<span lang="EN-US">--single-transaction</span>是一個更好的選擇，因為它根本不需要鎖定表。<span lang="EN-US">  
</span>請注意當匯出多個資料庫時，<span lang="EN-US">--lock-tables</span>分別為每個資料庫鎖定表。因此，該選項不能保證匯出檔中的表在資料庫之間的邏輯一致性。不同資料庫表的匯出狀態可以完全不同。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --lock-tables</span>`<span lang="EN-US">  
**--log-error**  
</span>附加警告和錯誤資訊到給定檔<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --log-error=/tmp/mysqldump_error_log.err</span>`<span lang="EN-US">  
**--master-data**  
</span>該選項將<span lang="EN-US">binlog</span>的位置和檔案名追加到輸出檔中。如果為<span lang="EN-US">1</span>，將會輸出<span lang="EN-US">CHANGE MASTER </span>命令；如果為<span lang="EN-US">2</span>，輸出的<span lang="EN-US">CHANGE MASTER</span>命令前添加注釋資訊。該選項將打開<span lang="EN-US">--lock-all-tables </span>選項，除非<span lang="EN-US">--single-transaction</span>也被指定（在這種情況下，全域讀鎖在開始匯出時獲得很短的時間；其他內容參考下面的<span lang="EN-US">--single-transaction</span>選項）。該選項自動關閉<span lang="EN-US">--lock-tables</span>選項。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --master-data=1;</span>`<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --master-data=2;</span>`<span lang="EN-US">  
**--max\_allowed\_packet**  
</span>伺服器發送和接受的最大包長度。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --max_allowed_packet=10240</span>`<span lang="EN-US">  
**--net\_buffer\_length**  
TCP/IP</span>和<span lang="EN-US">socket</span>連接的緩存大小。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --net_buffer_length=1024</span>`<span lang="EN-US">  
**--no-autocommit**  
</span>使用<span lang="EN-US">autocommit/commit </span>語句包裹表。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --no-autocommit</span>`<span lang="EN-US">  
**--no-create-db, -n**  
</span>只匯出數據，而不添加<span lang="EN-US">CREATE DATABASE </span>語句。<span lang="EN-US">  
mysqldump -uroot -p --host=localhost --all-databases --no-create-db  
**--no-create-info, -t**  
</span>只匯出數據，而不添加<span lang="EN-US">CREATE TABLE </span>語句。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --no-create-info</span>`<span lang="EN-US">  
**--no-data, -d**  
</span>不匯出任何資料，只匯出資料庫表結構。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --no-data</span>`<span lang="EN-US">  
**--no-set-names, -N**  
</span>等同于**<span lang="EN-US">--skip-set-charset</span>**<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --no-set-names</span>`<span lang="EN-US">  
**--opt**  
</span>等同於<span lang="EN-US">--add-drop-table, --add-locks, --create-options, --quick, --extended-insert, --lock-tables, --set-charset, --disable-keys </span>該選項預設開啟<span lang="EN-US">, </span>可以用<span lang="EN-US">--skip-opt</span>禁用<span lang="EN-US">.  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --opt</span>`<span lang="EN-US">  
**--order-by-primary**  
</span>如果存在主鍵，或者第一個唯一鍵，對每個表的記錄進行排序。在匯出<span lang="EN-US">MyISAM</span>表到<span lang="EN-US">InnoDB</span>表時有效，但會使得匯出工作花費很長時間。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --order-by-primary</span>`<span lang="EN-US">  
**--password, -p**  
</span>連接資料庫密碼<span lang="EN-US">  
**--pipe(windows**</span>**系統可用<span lang="EN-US">)</span>**<span lang="EN-US">  
</span>使用具名管道連接<span lang="EN-US">mysql  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --pipe</span>`<span lang="EN-US">  
**--port, -P**  
</span>連接資料庫埠號<span lang="EN-US">  
**--protocol**  
</span>使用的連線協定，包括：<span lang="EN-US">tcp, socket, pipe, memory.  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --protocol=tcp</span>`<span lang="EN-US">  
**--quick, -q**  
</span>不緩衝查詢，直接匯出到標準輸出。預設為打開狀態，使用<span lang="EN-US">--skip-quick</span>取消該選項。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases</span>`<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --skip-quick</span>`<span lang="EN-US">  
**--quote-names,-Q**  
</span>使用（<span lang="EN-US">'</span>）引起表和列名。預設為打開狀態，使用<span lang="EN-US">--skip-quote-names</span>取消該選項。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases</span>`<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --skip-quote-names</span>`<span lang="EN-US">  
**--replace**  
</span>使用<span lang="EN-US">REPLACE INTO </span>取代<span lang="EN-US">INSERT INTO.  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --replace</span>`<span lang="EN-US">  
**--result-file, -r**  
</span>直接輸出到指定檔中。該選項應該用在使用回車換行對（<span lang="EN-US">\\r\\n</span>）換行的系統上（例如：<span lang="EN-US">DOS</span>，<span lang="EN-US">Windows</span>）。該選項確保只有一行被使用。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --result-file=/tmp/mysqldump_result_file.txt</span>`<span lang="EN-US">  
**--routines, -R**  
</span>匯出存儲過程以及自訂函數。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --routines</span>`<span lang="EN-US">  
**--set-charset**  
</span>添加<span lang="EN-US">'SET NAMES default\_character\_set'</span>到輸出檔。預設為打開狀態，使用<span lang="EN-US">--skip-set-charset</span>關閉選項。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases</span>`<span lang="EN-US">  
m</span>`<span lang="EN-US">ysqldump -uroot -p --host=localhost --all-databases --skip-set-charset</span>`<span lang="EN-US">  
**--single-transaction**  
</span>該選項在匯出資料之前提交一個<span lang="EN-US">BEGIN SQL</span>語句，<span lang="EN-US">BEGIN </span>不會阻塞任何應用程式且能保證匯出時資料庫的一致性狀態。它只適用於多版本存儲引擎，僅<span lang="EN-US">InnoDB</span>。本選項和<span lang="EN-US">--lock-tables </span>選項是互斥的，因為<span lang="EN-US">LOCK TABLES </span>會使任何掛起的事務隱含提交。要想匯出大表的話，應結合使用<span lang="EN-US">--quick </span>選項。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --single-transaction</span>`<span lang="EN-US">  
**--dump-date**  
</span>將匯出時間添加到輸出檔中。預設為打開狀態，使用<span lang="EN-US">--skip-dump-date</span>關閉選項。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases</span>`<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --skip-dump-date</span>`<span lang="EN-US">  
**--skip-opt**  
</span>禁用<span lang="EN-US">–opt</span>選項<span lang="EN-US">.  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --skip-opt</span>`<span lang="EN-US">  
**--socket,-S**  
</span>指定連接<span lang="EN-US">mysql</span>的<span lang="EN-US">socket</span>檔位置，預設路徑<span lang="EN-US">/tmp/mysql.sock  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --socket=/tmp/mysqld.sock</span>`<span lang="EN-US">  
**--tab,-T**  
</span>為每個表在給定路徑創建<span lang="EN-US">tab</span>分割的文字檔。注意：僅僅用於<span lang="EN-US">mysqldump</span>和<span lang="EN-US">mysqld</span>伺服器運行在相同機器上。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost test test --tab="/home/mysql"</span>`<span lang="EN-US">  
**--tables**  
</span>覆蓋<span lang="EN-US">--databases (-B)</span>參數，指定需要匯出的表名。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --databases test --tables test</span>`<span lang="EN-US">  
**--triggers**  
</span>匯出觸發器。該選項預設啟用，用<span lang="EN-US">--skip-triggers</span>禁用它。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --triggers</span>`<span lang="EN-US">  
**--tz-utc**  
</span>在匯出頂部設置時區<span lang="EN-US">TIME\_ZONE='+00:00' </span>，以保證在不同時區匯出的<span lang="EN-US">TIMESTAMP </span>資料或者資料被移動其他時區時的正確性。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --tz-utc</span>`<span lang="EN-US">  
**--user, -u**  
</span>指定連接的用戶名。<span lang="EN-US">  
**--verbose, --v**  
</span>輸出多種平臺資訊。<span lang="EN-US">  
**--version, -V**  
</span>輸出<span lang="EN-US">mysqldump</span>版本資訊並退出<span lang="EN-US">  
**--where, -w**  
</span>只轉儲給定的<span lang="EN-US">WHERE</span>條件選擇的記錄。請注意如果條件包含命令解釋符專用空格或字元，一定要將條件引用起來。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --where=" user='root'"</span>`<span lang="EN-US">  
**--xml, -X**  
</span>匯出<span lang="EN-US">XML</span>格式<span lang="EN-US">.  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --xml</span>`<span lang="EN-US">  
**--plugin\_dir**  
</span>用戶端外掛程式的目錄，用於相容不同的外掛程式版本。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --plugin_dir="/usr/local/lib/plugin"</span>`<span lang="EN-US">  
**--default\_auth**  
</span>用戶端外掛程式默認使用權限。<span lang="EN-US">  
</span>`<span lang="EN-US">mysqldump -uroot -p --host=localhost --all-databases --default-auth="/usr/local/lib/plugin/<PLUGIN>"</span>`

# MySQL_帳號管理

### 1.新增帳號

#### 新增帳號

```SQL
--新增使用者 test, 密碼 newpassword, 允許任何來源 %
CREATE USER 'test'@'%' IDENTIFIED BY 'newpassword';

--新增使用者 test, 密碼 newpassword, 只允許本機(localhost)
CREATE USER 'test'@'localhost' IDENTIFIED BY 'newpassword';
```

#### 賦予權限

```SQL
--所有權限
grant all privileges ON *.* TO 'test'@'%';

--db1 所有查詢權限
grant select ON 'db1'.* TO 'test'@'%';

--更新權限
flush privileges;
```

---

### 2.修改密碼

#### 查詢帳號

```shell
#先找到user

mysql> select host,user from user;
+-----------+---------------+
| host      | user          |
+-----------+---------------+
| localhost | bookstackuser |
| localhost | mysql.session |
| localhost | mysql.sys     |
| localhost | root          |
+-----------+---------------+
4 rows in set (0.00 sec)

```

#### 修改密碼

```shell
#修改密碼
mysql> alter user 'root'@'localhost' identified by 'sa1234Asdf';
#更新
mysql> flush privileges;
Query OK, 0 rows affected (0.03 sec)

```

---

### 3.忘記密碼(密碼回復)

1. 修改設定檔 /etc/my.cnf
2. 服務重啟登入
3. 修改密碼
4. 恢復設定檔 /etc/my.cnf
5. 重啟服務

#### 修改設定檔 /etc/my.conf

```shell
#新增skip-grant-tables
[mysqld]
skip-grant-tables

```

#### 服務重啟登入

```shell
[root@localhost ~]# systemctl restart mysqld
[root@localhost ~]# mysql
```

#### 修改密碼

```shell
#修改密碼
mysql> alter user 'root'@'localhost' identified by 'newpassword'; 

#如果出現以下錯誤
ERROR 1290 (HY000): The MySQL server is running with the --skip-grant-tables option so it cannot execute this statement
## 先更新權限在修改密碼
mysql> flush privileges;
Query OK, 0 rows affected (0.03 sec)


#更新權限
mysql> flush privileges;
Query OK, 0 rows affected (0.03 sec)

```

#### 恢復設定檔 /etc/my.conf

```shell
#移除(註解) skip-grant-tables
[mysqld]
#skip-grant-tables

```

#### 重啟服務

```shell
[root@localhost ~]# systemctl restart mysqld
```

# Nginx

# 【錯誤處理】【Nginx】client intended to send too large body

查了一下資料發現造成的原因為 web server 接收 request body 的大小設定

- apache:  
    `LimitRequestBody` 預設為 0(unlimit)
- nginx:  
    `client_max_body_size` 預設為 1m

根據以下步驟更改一下 nginx 設定，就沒問題了

```Nginx
http {
    ...
 
    # set request body size unlimit
    client_max_body_size 0;
    ...
}
```

```
nginx -s reload
```

# 【Lua】【型別】 字串- 數字

- nil
- boolean
- number
- string
- function
- userdata
- thread
- table

## Number( 數字)

#### 字串轉數字

```Lua
tonumber("1.0")
```

#### 數字轉字串

```Lua
tostring(1.0)
```

#### 自動轉型

```Lua
-- Lua會自動轉型，如果一個字串與數字相加，會嘗試將字串轉換為數字
"1.0" + 2 -- => 3.0

-- 如果一個數字和字串使用串接，會嘗試將數字轉換成字串:
"1.0"..2 -- => 1.02
```

```Lua
-- 數字轉字串
1.0 .. ""

-- 字串轉數字
1.0 + 0 
```

---

## string (字串)

使用單引號或雙引號包起來：

```Lua
-- 單行
s1 = 'string'
s2 = "string"

-- 多行
s3 = [[
multiple
string
]]
```

#### 字串長度

```Lua
-- #
print(#"abc") -- => 3

-- string.len()
rint(string.len("abc")) -- => 3

-- 文字會有些不同
-- note: with UTF-8
print(string.len("我")) -- equal print(#"我") => 3

local utf8 = require "utf8"
print(utf8.len("你好，世界！")) -- => 6
```

####  文字串接

```Lua
-- 使用兩個點 .
print("Hello, " .. "World")  -- Hello, World

-- 多文字串接建議使用table.concat

local strs = {"Hello"," ","World"}
print(table.concat(strs,""))  -- Hello, World
```

```Lua
-- format
local string = require "string"
print(string.format("%s !! %s","Hello","World")) -- > Hello !! World

```

---

`file`的資料型態--`userdata`

```Lua
do
  local f <close> = io.open("text.txt",'w')
  print(type(f)) -- Output: userdata
end
```

# 【Lua】【型別】 函數function

#### 函數宣告

函數可以使用`function`來做宣告，並以`end`結束

```Lua
function hello()
  print("Hello, World")
end

-- 等同以下
hello = function()
  print("Hello, World")
end
```

```Lua
function genFun()
  return function()
    print("Hello, World")
  end
end


function callFun(f)
  f()
end


local _f = genFun() -- # get a function
callFun(_f) -- print("Hello, World")
```

#### 函數傳遞

```Lua
function plus(a, b)
    print(a,b, a + b)
    return a+b ,a ,b
end

local anser, A, B = plus(1,2)
print(A.."+"..B.."="..anser)

-- 1	2	3
-- 1+2=3
```

#### 回傳值規則

若返回值個數大於接收變量的個數，多餘的返回值會被忽略掉； 若返回值個數小 於參數個數，從左向右，沒有被返回值初始化的變量會被初始化為 nil。

```Lua
function init() --init 函数 返回两个值 1 和 "lua"
return 1, "lua"
end
x = init()
print(x)
x, y, z = init()
print(x, y, z)

--output
1
1 lua nil
```

当一个函数有一个以上返回值，且函数调用不是一个列表表达式的<span style="background-color: #f1c40f;">最后一个元素</span>，那么函数 调用只会产生一个返回值, 也就是第一个返回值。

```Lua
local function init() -- init 函数 返回两个值 1 和 "lua"
return 1, "lua"
end
local x, y, z = init(), 2 -- init 函数的位置不在最后，此时只返回 1
print(x, y, z) -->output 1 2 nil
local a, b, c = 2, init() -- init 函数的位置在最后，此时返回 1 和 "lua"
print(a, b, c) -->output 2 1 
```

####  

```Lua
local function init() -- init 函数 返回两个值 1 和 "lua"
return 1, "lua"
end
local x, y, z = init(), 2 -- init 函数的位置不在最后，此时只返回 1
print(x, y, z) -->output 1 2 nil
local a, b, c = 2, init() -- init 函数的位置在最后，此时返回 1 和 "lua"
print(a, b, c) -->output 2 1 
```

#### 不定長參數

```Lua
function hello(...)
  local members = {...}
  print("有多少成員: ", #members)
  for _,m in pairs(members) do
    print("Hello, "..m)
  end
end

-- hello("Bob", "Lua", "Luna", "Selene")
-- 有多少成員: 	4
-- Hello, Bob
-- Hello, Lua
-- Hello, Luna
-- Hello, Selene

```

```Lua
function mylog(...)
	local arr = {...}
	table.insert(arr,1,"\n=====[mylog_begin]=====")
	table.insert(arr,"=====[mylog_end]=====\n")
	local print_str = table.concat(arr,"\n")
	-- for key,value in pairs(arr) do
	-- 	-- ngx.say(key..":"..value)
	-- 	ngx.say(value)
	-- end	
	ngx.log(ngx.ERR,print_str)
end


mylog("開始",1,2,3,"結束")
-- =====[mylog_begin]=====
-- 開始
-- 1
-- 2
-- 3
-- 結束
-- =====[mylog_end]=====
```

#### 立即呼叫函式表達式(IIFE) 與 匿名函式

```Lua
(function ()
  print("Hello, World")
end)()
```

---

#### 多值返回

```Lua
function get(dict, key, default)
  return (dict[key] or default), dict[key] ~= nil
end

_d = {}
_v, found = get(_d, "key", "value")
print(_v) -- => Output: value
print(found) -- => Output: false
```

---

```Lua
function Person(args)
  local person = args
  person.name = args.name or "Bob"
  person.age = args.age or 20
  person.address = args.address or ""
  return person
end

p1 = Person {
  "位置參數1",
  age = 13,
  address = "secret",
  "位置參數2",
}

print("Name:    ", p1.name)
print("Age:     ", p1.age)
print("Address: ", p1.address)
print("-------------")
print("位置參數：", p1[1], p1[2])
```

```
Name: Bob
Age: 13
Address: secret

位置參數： 位置參數1 位置參數2
```

# 常用的內建函數

## 基本

- ipairs
- pairs
- print
- require
- tonumber
- tostring
- type

## 輸入/輸出

估計本系列不怎麼會提到，先放這XD。

- io.close
- io.input
- io.lines
- io.open
- io.output
- io.popen
- io.read
- io.stderr
- io.stdin
- io.stdout
- io.tmpfile
- io.type
- io.write
- file:close
- file:flush
- file:lines
- file:read
- file:seek
- file:write

## 數學

- math.abs
- math.ceil
- math.exp
- math.floor
- math.huge (無限大)
- math.log
- math.max
- math.maxinteger
- math.min
- math.mininteger
- math.pi (PI 常數)
- math.random
- math.randomseed
- math.tointeger
- math.type

## 作業系統

- os.clock
- os.date
- os.difftime
- os.execute
- os.exit
- os.getenv
- os.time

> Lua 5.1前一個和`os.getenv`很像的函數`setfenv`其實也蠻常用的，後來被移除，改用其他方式。  
> 當前還有很多是使用Lua 5.1，這雖然是需要知道的事情，不過其實其與作業系統無關。

## 字串

- string.byte
- string.char
- string.find
- string.format
- string.len
- string.reverse
- string.sub
- utf8.char
- utf8.codes
- utf8.len

# 【Lua】【型別】 nil - 布林

## nil

`nil`是Lua裡的一個特殊值，**代表什麼也沒有**。其型別也是`nil`

```Lua
type(nil) -- => nil
```

## 布林

#### <span class="s1">布林值只有</span><span style="background-color: #ffff99;">true</span><span class="s1">和</span><span style="background-color: #ffff99;">false</span>

#### 只要不是<span style="background-color: #ffff00;"><span style="font-family: Lucida Console, DejaVu Sans Mono, Ubuntu Mono, Monaco, monospace;"><span style="font-size: 19.5922px; white-space: pre-wrap; background-color: #ffff00;">nil</span></span></span>或是<span style="background-color: #ffff00;"><span style="font-family: Lucida Console, DejaVu Sans Mono, Ubuntu Mono, Monaco, monospace;"><span style="font-size: 19.5922px; white-space: pre-wrap; background-color: #ffff00;">false</span></span></span>都為真， **包含0、空表、空字串** 。

```Lua
if 0 then
  print("0 is true")
end

if {} then
  print("{} is true")
end

if "" then
  print [["" is true]]
end
```

#### ~= 不等於

```Lua
weeks = {"週一","週二","週三","週四","週五","週六","週日", "Opps"}

if #weeks ~= 7 then
  error("Weeks length must is 7.")
end
```

# 【Lua】【流程控制】for

```Lua
-- for i = {起始值}, {結束值}, {step] do
for i = 1, 10, 2 do
  print(i)
end


-- 1
-- 3
-- 5
-- 7
-- 9
```

迭代

```Lua
array = {"one", "two", "three"}

for i, v in ipairs(array) do
  print(i, v)
end

-- 1	one
-- 2	two
-- 3	three

```

```Lua
array = {"one", "two", "three"}
 
for i in ipairs(array) do
  print(i, array[i])
end

-- 1	one
-- 2	two
-- 3	three
```

```Lua
tb = {
  ["key1"] = "one",
  ["key2"] = "two",
  ["key3"] = "three",
}

for k, v in pairs(tb) do
  print(k, v)
end

-- key3	three
-- key1	one
-- key2	two
```

# 【Lua】【流程控制】if

####  

```Lua
if true then
  print("if block")
elseif true then
  print("elseif block")
else
  print("else")
end
```

```Lua
if not true then
  print("not true")
else
  print("else")
end
```

#### <span style="color: #ff0000;">elseif</span> 中間<span style="background-color: #ffff00;">沒有空白</span>要注意

```Lua
if true then
  print("if block")
else if true then
  print("elseif block")
else
  print("else")
end
end

-- 其實等於

if true then
    print("if block")
else 
    if true then
        print("elseif block")
    else
        print("else")
    end
end

```

# 【Lua】【流程控制】while

```Lua
destination = 5  -- 終點位置
current_pos = 0  -- 目前位置

print([[馬拉松賽跑
============]])

print("終點位於："..destination)
print("目前在："..current_pos)

while current_pos < destination do
  current_pos = current_pos + 1
  print("往前跑1㎞，目前位於"..current_pos)
end

print("抵達終點，目前位於"..current_pos)
```

```
馬拉松賽跑
============
終點位於：5
目前在：0
往前跑1㎞，目前位於1
往前跑1㎞，目前位於2
往前跑1㎞，目前位於3
往前跑1㎞，目前位於4
往前跑1㎞，目前位於5
抵達終點，目前位於5


```

# 【Lua】【優化】請求返回後繼續執行

ngx.eof 關閉連線，data 返還user，後續代碼繼續進行

```Lua
local response, user_stat = logic_func.get_response(request)
ngx.say(response)
ngx.eof()
if user_stat then
local ret = db_redis.update_user_data(user_stat)
end
```

# 【Lua】安裝 luarocks

[https://github.com/luarocks/luarocks/wiki/Download](https://github.com/luarocks/luarocks/wiki/Download)

[https://github.com/luarocks/luarocks/wiki/Installation-instructions-for-Unix](https://github.com/luarocks/luarocks/wiki/Installation-instructions-for-Unix)

```shell
apt-get update
apt install build-essential libreadline-dev unzip wget zlib1g-dev -y

#安裝 luarocks
wget https://luarocks.org/releases/luarocks-3.8.0.tar.gz && tar zxpf luarocks-3.8.0.tar.gz && cd luarocks-3.8.0 
./configure 
make && make install

#安裝 lua-resty-http
luarocks install lua-resty-http
#安裝 lua-ffi-zlib
luarocks install lua-ffi-zlib
```

### install lua-zlib

<div id="bkmrk-https%3A%2F%2Fblog.51cto.c">[https://blog.51cto.com/u\_5650011/5394910](https://blog.51cto.com/u_5650011/5394910)</div><div id="bkmrk-"></div>```
cd /usr/local
wget https://github.com/brimworks/lua-zlib/archive/master.zip
unzip lua-zlib-master.zip
```

```
cd /usr/local/lua-zlib-master
cmake -DLUA_INCLUDE_DIR=/usr/local/openresty/luajit/include/luajit-2.1 -DLUA_LIBRARIES=/usr/local/openresty/luajit/lib -DUSE_LUAJIT=ON -DUSE_LUA=OFF
```

```
make
cp zlib.so /usr/local/openresty/lualib/zlib.so
```

# 【Lua】註解

### 單行註解

```Lua
 --  這是註解
```

### 多行註解

```Lua
-- 使用 --[[ 內文 ]]-- 包起來

--[[
 function log_header_body()
        -- 取出列表編號
        local t = cjson.decode(ngx.req.get_body_data())
        local body =  cjson.encode(t)
        ngx.log(ngx.ERR, body)
        local headers = cjson.encode(ngx.req.get_headers())
        ngx.log(ngx.ERR, headers)
    
end
]]--
```

#### 特數多行註解

可在--\[ ${這中間插入任意數量=}\[ ..... \] ${這中間插入任意數量=}\]--

```Lua
--[===[
Some Comments
==============
特別插入--]]  但實際註釋還未結束
--]===] print("這段不是註釋") -- end comment
```

# 【Lua】load

與 javascipt eval 相同－動態產生並執行程式碼

```Lua
-- load不回直接執行，其實其返回一個包裝函式：

f = load[[g2 = 2]]
print(type(f)) -- => function

print(g2) -- => Output: nil
f()
print(g2) -- => Output: 2
```

# 【Lua】ngx_lua_module

[![image-1676368742762.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-02/scaled-1680-/4ZBk8mEgtc6Hwo7h-image-1676368742762.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-02/4ZBk8mEgtc6Hwo7h-image-1676368742762.png)

<table class="full-width fixed-word-wrap" id="bkmrk-%E6%8C%87%E4%BB%A4-%E8%AA%AA%E6%98%8E-set_by_lua%2A-%E6%B5%81%E7%A8%8B" style="height: 232px;"><thead><tr style="height: 29px;"><td style="height: 29px; width: 154.539px;">指令</td><td style="height: 29px; width: 423px;">說明</td></tr></thead><tbody><tr style="height: 29px;"><td style="height: 29px; width: 154.539px;">set\_by\_lua\*</td><td style="height: 29px; width: 423px;">流程分支處理判斷變量初始化</td></tr><tr style="height: 29px;"><td style="height: 29px; width: 154.539px;">rewrite\_by\_lua\*</td><td style="height: 29px; width: 423px;">轉發、重定向、緩存等功能</td></tr><tr style="height: 29px;"><td style="height: 29px; width: 154.539px;">access\_by\_lua\*</td><td style="height: 29px; width: 423px;">IP 准入、身份驗證、接口權限、解密</td></tr><tr style="height: 29px;"><td style="height: 29px; width: 154.539px;">content\_by\_lua\*</td><td style="height: 29px; width: 423px;">內容生成</td></tr><tr style="height: 29px;"><td style="height: 29px; width: 154.539px;">header\_filter\_by\_lua\*</td><td style="height: 29px; width: 423px;">響應頭部過濾處理，可以添加響應頭</td></tr><tr style="height: 29px;"><td style="height: 29px; width: 154.539px;">body\_filter\_by\_lua\*</td><td style="height: 29px; width: 423px;">響應體過濾處理，例如轉換響應體</td></tr><tr style="height: 29px;"><td style="height: 29px; width: 154.539px;">log\_by\_lua\*</td><td style="height: 29px; width: 423px;">異步完成日誌記錄，日誌可以記錄在本地，還可以同步到其他機器</td></tr></tbody></table>

儘管僅使用單個階段的指令content\_by\_lua\*就可以完成以上職責，但是把邏輯劃分在不同階段，更加容易維護。

[https://blog.gmem.cc/openresty-study-note](https://blog.gmem.cc/openresty-study-note)

[https://github.com/openresty/lua-nginx-module](https://github.com/openresty/lua-nginx-module)

# 【Lua】Nginx 變量

<div id="bkmrk-%E4%BD%BF%E7%94%A8ng%E8%AE%8A%E9%87%8F"><span class="graybg"><span>使用Ng變量</span></span></div><span>要在OpenResty中引用Nginx變量，可以使用 </span><span class="crayon-syntax crayon-syntax-inline  crayon-theme-gmem-github crayon-theme-gmem-github-inline crayon-font-consolas" id="bkmrk-ngx%C2%A0.%C2%A0var%C2%A0.%C2%A0variable"><span class="crayon-pre crayon-code"><span class="crayon-v"><span>ngx </span></span><span class="crayon-sy"><span>. </span></span><span class="crayon-t"><span>var </span></span><span class="crayon-sy"><span>. </span></span><span class="crayon-v"><span>VARIABLE</span></span></span></span><span>，要將變量從字符串轉換為數字，可以使用 </span><span class="crayon-syntax crayon-syntax-inline  crayon-theme-gmem-github crayon-theme-gmem-github-inline crayon-font-consolas" id="bkmrk-tonumber"><span class="crayon-pre crayon-code"><span class="crayon-v"><span>tonumber</span></span></span></span><span>函數。</span>

```Lua
-- 黑名单
local black_ips = {["127.0.0.1"]=true}
 
-- 当前客户端IP
local ip = ngx.var.remote_addr
if true == black_ips[ip] then
    -- 返回相应的HTTP状态码
    ngx.exit(ngx.HTTP_FORBIDDEN)
end
```

<span>經常用到的Ng變量如下表：</span>

<table class="full-width fixed-word-wrap" id="bkmrk-%E8%AE%8A%E9%87%8F-%E8%AA%AA%E6%98%8E-arg_name-%E8%AB%8B%E6%B1%82%E4%B8%AD%E7%9A%84n"><thead><tr><td><span>變量</span></td><td><span>說明</span></td></tr></thead><tbody><tr><td><span>arg\_name</span></td><td><span>請求中的name參數</span></td></tr><tr><td><span>args</span></td><td><span>請求中的參數</span></td></tr><tr><td><span>binary\_remote\_addr</span></td><td><span>遠程地址的二進製表示</span></td></tr><tr><td><span>body\_bytes\_sent</span></td><td><span>已發送的消息體字節數</span></td></tr><tr><td><span>content\_length</span></td><td><span>HTTP請求信息裡的"Content-Length"</span></td></tr><tr><td><span>content\_type</span></td><td><span>請求信息裡的"Content-Type"</span></td></tr><tr><td><span>document\_root</span></td><td><span>針對當前請求的根路徑設置值</span></td></tr><tr><td><span>document\_uri</span></td><td><span>與$uri相同; 比如/test2/test.php</span></td></tr><tr><td><span>host</span></td><td><span>請求信息中的"Host"，如果請求中沒有Host行，則等於設置的服務器名</span></td></tr><tr><td><span>hostname</span></td><td><span>機器名使用gethostname系統調用的值</span></td></tr><tr><td><span>http\_cookie</span></td><td><span>Cookie信息</span></td></tr><tr><td><span>http\_referer</span></td><td><span>引用地址</span></td></tr><tr><td><span>http\_user\_agent</span></td><td><span>客戶端代理信息</span></td></tr><tr><td><span>http\_via</span></td><td><span>最後一個訪問服務器的Ip地址。</span></td></tr><tr><td><span>http\_x\_forwarded\_for</span></td><td><span>相當於網絡訪問路徑</span></td></tr><tr><td><span>is\_args</span></td><td><span>如果請求行帶有參數，返回“?”，否則返回空字符串</span></td></tr><tr><td><span>limit\_rate</span></td><td><span>對連接速率的限制。此變量支持寫入：</span>

<div data-settings=" no-popup minimize scroll-mouseover"><div data-settings=" show"><div><div><div></div></div><div><div></div></div><div><div></div></div><span class="crayon-language">Lua</span></div></div><div></div><div></div><div><table class="crayon-table"><tbody><tr class="crayon-row"><td class="crayon-nums " data-settings="show"><div><div data-line="crayon-63eb517b82d78653021122-1">1</div><div data-line="crayon-63eb517b82d78653021122-2">2</div></div></td><td class="crayon-code"><div><div><span class="crayon-c">-- 设置当前请求的响应传递速率限制</span></div><div><span class="crayon-v">ngx</span><span class="crayon-sy">.</span><span class="crayon-v">var</span><span class="crayon-sy">.</span><span class="crayon-v">limit\_rate</span><span class="crayon-h"> </span><span class="crayon-o">=</span><span class="crayon-h"> </span><span class="crayon-cn">1000</span> </div></div></td></tr></tbody></table>

</div></div></td></tr><tr><td><span>nginx\_version</span></td><td><span>當前運行的nginx版本號</span></td></tr><tr><td><span>pid</span></td><td><span>Worker進程的PID</span></td></tr><tr><td><span>query\_string</span></td><td><span>與$args相同</span></td></tr><tr><td><span>realpath\_root</span></td><td><span>按root指令或alias指令算出的當前請求的絕對路徑。其中的符號鏈接都會解析成真是文件路徑</span></td></tr><tr><td><span>remote\_addr</span></td><td><span>客戶端IP地址</span></td></tr><tr><td><span>remote\_port</span></td><td><span>客戶端端口號</span></td></tr><tr><td><span>remote\_user</span></td><td><span>客戶端用戶名，認證用</span></td></tr><tr><td><span>request</span></td><td><span>用戶請求</span></td></tr><tr><td><span>request\_body</span></td><td><span>這個變量（0.7.58+）包含請求的主要信息。在使用proxy\_pass或fastcgi\_pass指令的location中比較有意義</span></td></tr><tr><td><span>request\_body\_file</span></td><td><span>客戶端請求主體信息的臨時文件名</span></td></tr><tr><td><span>request\_completion</span></td><td><span>如果請求成功，設為"OK"；如果請求未完成或者不是一系列請求中最後一部分則設為空</span></td></tr><tr><td><span>request\_filename</span></td><td><span>當前請求的文件路徑名，比如/opt/nginx/www/test.php</span></td></tr><tr><td><span>request\_method</span></td><td><span>請求的方法，比如"GET"、"POST"等</span></td></tr><tr><td><span>request\_uri</span></td><td><span>請求的URI，帶參數</span></td></tr><tr><td><span>scheme</span></td><td><span>所用的協議，比如http或者是https</span></td></tr><tr><td><span>server\_addr</span></td><td><span>服務器地址，如果沒有用listen指明服務器地址，使用這個變量將發起一次系統調用以取得地址(造成資源浪費)</span></td></tr><tr><td><span>server\_name</span></td><td><span>請求到達的服務器名</span></td></tr><tr><td><span>server\_port</span></td><td><span>請求到達的服務器端口號</span></td></tr><tr><td><span>server\_protocol</span></td><td><span>請求的協議版本，"HTTP/1.0"或"HTTP/1.1"</span></td></tr><tr><td><span>uri</span></td><td><span>請求的URI，可能和最初的值有不同，比如經過重定向之類的</span></td></tr></tbody></table>

# 【Lua】ngx.say 與 ngx.print 差異

差異在 ngx.say 會加入一個換行符號

# 【Lua】SSL相關指令執行順序

<div drawio-diagram="522"><img src="https://bookstack.treemanou.com/uploads/images/drawio/2023-02/dyJfouYkGTnWwOup-drawing-3-1676368425.png" alt=""/></div>

# 【Lua】table

### 模擬陣列用法

#### <span style="color: #ff0000;">注意</span> lua 索引 從<span style="background-color: #ffff00;"> 1</span> 開始

```Lua
-- 宣告 table
lang = {
  "C",
  "C#",
  "C++",
  "Java",
  "Swift",
  "Python",
  "Haskell",
}

-- 使用 ipairs 迭代 lang
for i in ipairs(lang) do
    print(i, #lang[i], lang[i])
end

-- 以下寫法相同
for i,v in ipairs(lang) do
    print(i, #v, v)
end


-- 1	1	C
-- 2	2	C#
-- 3	3	C++
-- 4	4	Java
-- 5	5	Swift
-- 6	6	Python
-- 7	7	Haskell
```

###  新增資料 =&gt; table.insert

```Lua
lang = {
  "C",
  "C#",
  "C++",
  "Java",
  "Swift",
  "Python",
  "Haskell",
}

-- 使用table.insert
table.insert(lang, "lua")
-- 使用索引
lang[9] = "Javascript"

-- 1	1	C
-- 2	2	C#
-- 3	3	C++
-- 4	4	Java
-- 5	5	Swift
-- 6	6	Python
-- 7	7	Haskell
-- 8	3	lua
-- 9	10	Javascript
```

### 刪除資料 =&gt; 設為 nil

```Lua
lang = {
  "C",
  "C#",
  "C++",
  "Java",
  "Swift",
  "Python",
  "Haskell",
}

-- 刪除總是山最後一筆，不然會有意外錯誤
lang[8] = nil

for i,v in ipairs(lang) do
    print(i, #v, v)
end
-- 1	1	C
-- 2	2	C#
-- 3	3	C++
-- 4	4	Java
-- 5	5	Swift
-- 6	6	Python
-- 7	7	Haskell
```

### 類map(key,vlaue)宣告

#### key 不要<span style="color: #ff0000;">文字</span>,<span style="color: #ff0000;">數字</span>混和宣告

```Lua
person = {
  ["name"] = "Bob",
  ["age"] = 25,
}

-- key不為數字－以上相同
person = {
  name = "Bob",
  age = 25,
}

-- 陣列用法
arr = {
  [1] = 1,
  [2] = 2,
  [3] = 3,
  [4] = 4,
}
```

### Key 值範圍

key可以是除了`nil`和`NaN`(Not a Number)以外的任何型別。

```Lua
obj = {} -- 建立一個空表
obj[1] = 1 -- 整數是合法的key值
obj[1.0] = 2 -- 浮點數是合法的key值
obj["string"] = 1 -- 字串是合法的key值
obj[math.huge] = 1 -- inf是合法的key值

--[[ 
要注意的是 obj[1] 和 obj[1.0]相同
其obj[1]和obj[1.0]最終值為2
--]]

----------------

print(obj[nil]) --> nil
obj[nil] = 1 --> Error: nil不是合法的key值，儘管取值不會出錯

print(obj[0/0]) --> nil
print(0/0) --> -nan
obj[0/0] = 1 --> Error: NaN不是合法的key值
```

# 【Lua】vscode 外掛

[https://marketplace.visualstudio.com/items?itemName=sumneko.lua](https://marketplace.visualstudio.com/items?itemName=sumneko.lua)

[![image-1677036811172.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-02/scaled-1680-/c9fZHREKy1LWZx5U-image-1677036811172.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-02/c9fZHREKy1LWZx5U-image-1677036811172.png)

# 【Nginx】config 詳解

#### 基本配置區塊

```Nginx
#config 區塊 基本配置
...                   # 全域性區塊
 
event{                # events 區塊
        ...
}
http{                 # http 區塊
    server{           # server 區塊
        location{     # location 區塊
            ...
        }
    }
}
```

#### server 區塊的配置

```Nginx
server{
      listen  80;
      listen [::]80;
      server_name  example.com  example2.com; 
    location /{
        root /usr/share/nginx/html;
    	index index.html index.htm;
    }
}

#listen 80; 代表監聽所有 ipv4 的位址
#listen [::]80; 代表監聽所有 ipv6 的位址
#server_name 是你的 Domain 名稱。 可以使用空白listen 多個domain
#location 中則是指定對不同路徑要怎麼處理
```

```Nginx
# 如果沒有下deault_server; 設定，第一個server 區塊會變成預設所以有位被匹配到的設定
# 避免除錯上的困難，可設定一個預設區塊，在未匹配到server 時，顯示403錯誤
server {
  listen 80 default_server;
  server_name _;

  set $cache_status -;
  set $parameter -;
  set $response_body_size 0;
  location / {
  default_type 'text/html';
  echo "default server" ;
  return 403; # 403 forbidden
  }
}
	
```

#### 【反向代理】reverse proxy

```Nginx
server {
 
    listen 80;
    server_name www.example.com;
 
    location \ {  
           proxy_pass http://192.168.1.1:8080;  
           
           proxy_set_header Host 				$host;
           proxy_set_header X-Real-IP			$remote_addr;
           proxy_set_header X-Forwarded-For		$proxy_add_x_forwarded_for;
           proxy_set_header X-Forwarded-Proto	$scheme;
        }
}
```

####  【快取】實作圖片快取機制

```Nginx
server {
 
    listen 80;
    server_name www.example.com;
 
    location ~* \.(jpg|jpeg|png|css|js)$ {  
            expires 365d;  # 快取時間
            proxy_pass http://loaclhost:8080;  
        }
}
```

#### 【virtural host】實作 virtural host

```Nginx
# .web1
server {
    listen      80;
    server_name web1.example.com ;
    root /usr/loacl/nginx/web1;
    index index.html;
}
 
# .web2
server {
    listen      80;
    server_name web2.example.com ;
    root /usr/loacl/nginx/web2;
    index index.html;
}
 
```

#### 【負載平衡】 load balance

```Nginx
#Nginx 提供了以下三種 load balancing 方法：

#round-robin：預設值，會將請留輪流平均分配到每台伺服器上
#lest-connected：會將請求分配到目前連接數最少的伺服器上
#ip-hash：利用 hash-function 來決定使用者要被分配到的伺服器，此方法可以達到同一個使用者 (IP address) 每次連結的伺服器都是相同的
#如果要從默認值 round-robin 方法改成 lest-connected 或 ip-hash 的方法，只需要在第一行加上lest_conn; 或 ip_hash; 即可
upstream myapp {
    ip_hash;
    server srv1.example.com;
    server srv2.example.com;
    server srv3.example.com;
}
server {
  listen 80;
  location / {
    proxy_pass   http://myapp;
  }
}
```

```Nginx
# weight 默認值為 1，
# 以下的配置代表如果有 5 次新的請求，則會有 3 次被分配到 srv1 和分配各 1 次到 srv2 srv3 上
upstream myapp1 {
    server srv1.example.com weight=3;
    server srv2.example.com;
    server srv3.example.com;
    server bak.example.com; #backup 代表，所有伺服器都掛掉之後，此伺服器才會生效
}
server {
  listen 80;
  location / {
    proxy_pass   http://myapp1;
  }
}
```

---

#### 限制來源

```
location / {
    deny  192.168.1.1;
    allow 192.168.1.0/24;
    allow 10.1.1.0/16;
    allow 2001:0db8::/32;
    deny  all;
}
```

#### nginx.conf 詳細配置

```Nginx
#定义Nginx运行的用户和用户组
user www www;


#nginx進程數，建議設置為等於CPU總核心數。
worker_processes 8;


#全局錯誤日誌定義類型，[ debug | info | notice | warn | error | crit ]
error_log /var/log/nginx/error.log info;


#進程文件
pid /var/run/nginx.pid;


#一個nginx進程打開的最多文件描述符數目，理論值應該是最多打開文件數（系統的值ulimit -n）與nginx進程數相除，但是nginx分配請求並不均勻，所以建議與ulimit -n的值保持一致。
worker_rlimit_nofile 65535;


#工作模式與連接數上限
events
{
#參考事件模型，use [ kqueue | rtsig | epoll | /dev/poll | select | poll ]; epoll模型是Linux 2.6以上版本內核中的高性能網絡I/O模型，如果跑在FreeBSD上面，就用kqueue模型。
use epoll;
#單個進程最大連接數（最大連接數=連接數*進程數）
worker_connections 65535;
}

#設定http服務器
http
{
include mime.types; #文件擴展名與文件類型映射表
default_type application/octet-stream; #默認文件類型
#charset utf-8; #默認編碼
server_names_hash_bucket_size 128; #服務器名字的hash表大小
large_client_header_buffers 4 64k; #設定請求緩
autoindex on; #開啟目錄列表訪問，合適下載服務器，默認關閉。

#此指令設置 Nginx 使用的 DNS 解析器的配置。在這個例子中，使用了兩個 DNS 解析器的 IP 地址：8.8.8.8 和 168.95.1.1。
#ipv6=off 表示禁用 IPv6 解析。valid=300s 表示 DNS 解析的結果將在 300 秒（5 分鐘）內被緩存。
resolver 8.8.8.8 168.95.1.1 ipv6=off valid=300s;
  	
#此指令設置 DNS 解析的超時時間。如果在指定的時間內無法解析域名，Nginx 將中斷解析並返回錯誤。
resolver_timeout 3s;
  
client_body_buffer_size 256K; 	#此指令指定客戶端請求主體的緩衝區大小，這是用於存儲客戶端發送的請求主體數據的內存區域。
client_body_timeout 3s; 		#此指令設置接收客戶端請求主體的超時時間。如果在指定的時間內沒有接收到完整的請求主體，Nginx 將結束該連接。
client_header_buffer_size 64k;	#此指令指定用於存儲客戶端請求標頭的緩衝區大小。(?上傳文件大小限制?)
client_header_timeout 3s;		#此指令設置接收客戶端請求標頭的超時時間。如果在指定的時間內沒有接收到完整的請求標頭，Nginx 將結束該連接。
client_max_body_size 1m;		#此指令設置允許客戶端發送的最大請求主體大小。如果客戶端嘗試發送超過此大小的請求主體，Nginx 將返回 413 Request Entity Too Large 錯誤。
large_client_header_buffers 8 64k; #此指令設置用於存儲大型客戶端請求標頭的緩衝區大小。如果客戶端請求的標頭超過指定的大小，Nginx 將使用這些緩衝區來處理它。
keepalive_timeout 5s;			#此指令設置持久連接的超時時間。如果在指定的時間內沒有新的請求到達，Nginx 將關閉持久連接。
open_file_cache max=5000 inactive=20s; #此指令啟用了文件打開時的緩存機制，以加快文件的讀取速度。max 參數指定緩存的最大數量，inactive 參數指定文件在緩存中保持不活躍的時間。
open_file_cache_valid 30s;		#此指令設置緩存中的文件信息的有效期限。在此期限內，Nginx 將重用緩存的文件信息而不需要再次訪問磁盤。
open_file_cache_min_uses 8;		#此指令設置文件緩存機制中的最小使用次數。只有當某個文件被訪問的次數超過此設定值時，才會將該文件的信息緩存起來。
open_file_cache_errors on;		#此指令設置是否緩存文件打開期間的錯誤。當設置為 "on" 時，如果在打開文件時發生錯誤，Nginx 會將該錯誤信息緩存起來，以避免重複出現相同的錯誤。
send_timeout 10;				#此指令設置向客戶端發送數據的超時時間。如果在指定的時間內沒有將數據發送給客戶端，Nginx 將結束該連接。

#開啟高效文件傳輸模式，sendfile指令指定nginx是否調用sendfile函數來輸出文件，對於普通應用設為on，用於高效地將文件內容發送給客戶端，而無需將文件數據從磁盤讀入到用戶空間。
#如果用來進行下載等應用磁盤IO重負載應用，可設置為off，以平衡磁盤與網絡I/O處理速度，降低系統的負載。注意：如果圖片顯示不正常把這個改成off。
sendfile on;
  
sendfile_max_chunk 512k;		#此指令設置單次 sendfile 操作的最大數據塊大小。如果需要發送的文件數據大於此值，Nginx 將分段進行 sendfile 操作。
server_tokens off;				#此指令關閉 Nginx 的版本號顯示。在生產環境中，關閉版本號的顯示可以增加安全性，防止攻擊者利用已知漏洞進行攻擊。
tcp_nodelay on;					#此指令啟用 TCP 即時發送功能。它禁用了 Nagle 算法，從而降低了數據傳輸的延遲，但可能會增加網絡流量。
tcp_nopush  on;					#此指令啟用 TCP 壓縮機制。當服務器將數據發送給客戶端時，它將盡可能地將多個小的數據包合並為一個較大的數據包發送，從而減少了網絡傳輸的開銷。
types_hash_max_size 2048;		#此指令設置 Nginx 在處理 MIME 類型時使用的哈希表的大小。增大此值可以提高性能

#FastCGI相關參數是為了改善網站的性能：減少資源佔用，提高訪問速度。下面參數看字面意思都能理解。
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
fastcgi_buffer_size 64k;
fastcgi_buffers 4 64k;
fastcgi_busy_buffers_size 128k;
fastcgi_temp_file_write_size 128k;


#gzip模塊設置
gzip on; #開啟gzip壓縮輸出
gzip_min_length 1k; #最小壓縮文件大小
gzip_buffers 4 16k; #壓縮緩衝區
gzip_http_version 1.0; #壓縮版本（默認1.1，前端如果是squid2.5請使用1.0）
gzip_comp_level 2; #壓縮等級
gzip_types text/plain application/x-javascript text/css application/xml;
#壓縮類型，默認就已經包含text/html，所以下面就不用再寫了，寫上去也不會有問題，但是會有一個warn。
gzip_vary on;
#limit_zone crawler $binary_remote_addr 10m; #開啟限制IP連接數的時候需要使用


upstream blog.ha97.com {
#upstream的負載均衡，weight是權重，可以根據機器配置定義權重。weigth參數表示權值，權值越高被分配到的機率越大。
server 192.168.80.121:80 weight=3;
server 192.168.80.122:80 weight=2;
server 192.168.80.123:80 weight=3;
}


#虛擬主機的配置
server
{
#監聽端口
listen 80;
#域名可以有多個，用空格隔開
server_name www.ha97.com ha97.com;
index index.html index.htm index.php;
root /data/www/ha97;
location ~ .*\.(php|php5)?$
{
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi.conf;
}
#圖片緩存時間設置
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 10d;
}
#JS和CSS緩存時間設置
location ~ .*\.(js|css)?$
{
expires 1h;
}
#日誌格式設定
log_format access '$remote_addr – $remote_user [$time_local] “$request” '
'$status $body_bytes_sent “$http_referer” '
'”$http_user_agent” $http_x_forwarded_for';
#定義本虛擬主機的訪問日誌
access_log /var/log/nginx/ha97access.log access;


#對“/” 啟用反向代理
location / {
proxy_pass http://127.0.0.1:88;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
#後端的Web服務器可以通過X-Forwarded-For獲取用戶真實IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#以下是一些反向代理的配置，可選。
proxy_set_header Host $host;
client_max_body_size 10m; #允許客戶端請求的最大單文件字節數
client_body_buffer_size 128k; #緩衝區代理緩衝用戶端請求的最大字節數，
proxy_connect_timeout 90; #nginx跟後端服務器連接超時時間(代理連接超時)
proxy_send_timeout 90; #後端服務器數據回傳時間(代理髮送超時)
proxy_read_timeout 90; #連接成功後，後端服務器響應時間(代理接收超時)
proxy_buffer_size 4k; #設置代理服務器（nginx）保存用戶頭信息的緩衝區大小
proxy_buffers 4 32k; #proxy_buffers緩衝區，網頁平均在32k以下的設置
proxy_busy_buffers_size 64k; #高負荷下緩衝大小（proxy_buffers*2）
proxy_temp_file_write_size 64k;
#設定緩存文件夾大小，大於這個值，將從upstream服務器傳
}


#設定查看Nginx狀態的地址
location /NginxStatus {
stub_status on;
access_log on;
auth_basic “NginxStatus”;
auth_basic_user_file conf/htpasswd;
#htpasswd文件的內容可以用apache提供的htpasswd工具來產生。
}



#本地动静分离反向代理配置
#所有jsp的页面均交由tomcat或resin处理
location ~ .(jsp|jspx|do)?$ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8080;
}
#所有静态文件由nginx直接读取不经过tomcat或resin
location ~ .*.(htm|html|gif|jpg|jpeg|png|bmp|swf|ioc|rar|zip|txt|flv|mid|doc|ppt|pdf|xls|mp3|wma)$
{ expires 15d; }
location ~ .*.(js|css)?$
{ expires 1h; }
}
}
```

# 【Nginx】location 匹配規則

### 一般匹配

location \[=|~|~\*|^~\] /uri/ { ... }

<table id="bkmrk-%E6%A8%A1%E5%BC%8F-%E5%90%AB%E4%B9%89-location-%3D-%2Fur"><thead><tr><th>模式</th><th>含义</th></tr></thead><tbody><tr><td>location = /uri</td><td>= 表示精确匹配，只有完全匹配上才能生效</td></tr><tr><td>location ^~ /uri</td><td>^~ 开头对URL路径进行前缀匹配，并且在正则之前。</td></tr><tr><td>location ~ pattern</td><td>开头表示区分大小写的正则匹配</td></tr><tr><td>location ~\* pattern</td><td>开头表示不区分大小写的正则匹配</td></tr><tr><td>location /uri</td><td>不带任何修饰符，也表示前缀匹配，但是在正则匹配之后</td></tr><tr><td>location /</td><td>通用匹配，任何未匹配到其它location的请求都会匹配到，相当于switch中的default</td></tr></tbody></table>

<div id="bkmrk-"></div>實際使用規則至少有三個匹配定義

```Nginx
# 直接匹配网站根，通过域名访问网站首页比较频繁，使用这个会加速处理，官网如是说。 # 这里是直接转发给后端应用服务器了，也可以是一个静态首页
# 第一个必选规则
location = / {
    proxy_pass http://tomcat:8080/index
}
# 第二个必选规则是处理静态文件请求，这是 nginx 作为 http 服务器的强项
# 有两种配置模式，目录匹配或后缀匹配，任选其一或搭配使用 
location ^~ /static/ {
    root /webroot/static/;
}
location ~* \.(gif|jpg|jpeg|png|css|js|ico)$ {
    root /webroot/res/;
}
# 第三个规则就是通用规则，用来转发动态请求到后端应用服务器
# 非静态文件请求就默认是动态请求，自己根据实际把握
# 毕竟目前的一些框架的流行，带.php、.jsp后缀的情况很少了 location / {
    proxy_pass http://tomcat:8080/
}
```

依據檔案類型設置過期時間

```Nginx
location ~* \.(js|css|jpg|jpeg|gif|png|swf)$ {
    if (-f $request_filename) {
expires 1h;
break; }
}
```

禁止訪問某目錄

```Nginx
location ~* \.(txt|doc)${
    root /data/www/wwwroot/linuxtone/test;
    deny all;
}
```

---

### 內部命名位置

#### 命名位置的用途

- **`@` 開頭**: 這個符號用來表示這是一個內部使用的命名位置，不會與普通 URL 路徑混淆。
- **命名自由**: 你可以給命名位置起任何名稱，只要以 `@` 開頭並且不與其他位置塊或指令名稱衝突。

#### 如何使用命名位置

命名位置常用於內部重定向，當某些條件滿足時會跳轉到這些位置。通常用在 `try_files`、`error_page` 等指令中。

#### 命名位置的典型應用

- **靜態資源**: 先查找靜態文件，如果找不到則交給動態處理程序處理。
- **錯誤處理**: 如果遇到特定錯誤碼，重定向到特定的錯誤處理位置。
- **代理後備**: 如果特定條件下需要將請求代理到其他服務器。

#### 使用 `try_files` 與命名位置

```Nginx
server {
    listen 80;
    server_name example.com;

    # 主位置塊
    location / {
        # 嘗試匹配靜態文件，如果找不到則重定向到 @backend
        try_files $uri $uri/ @backend;
    }

    # 定義 @backend 內部重定向位置
    location @backend {
        # 添加自定義標頭
        add_header X-Cache-Status "MISS" always;
        # 設定代理地址
        proxy_pass http://backend_server;
    }
}

```

在這個例子中：

- `try_files $uri $uri/ @backend;` 指示 Nginx 先嘗試匹配 `$uri` 和 `$uri/`，如果都不匹配，則跳轉到 `@backend`。
- 在 `@backend` 位置塊中，添加一個自定義標頭並將請求代理到 `http://backend_server`。

#### 使用 `error_page` 與命名位置

假設你希望在遇到 404 錯誤時顯示自定義的錯誤頁面，並且如果特定條件下（例如 URL 中包含 `/api/`），將請求代理到其他服務器。可以這樣配置：

```Nginx
server {
    listen 80;
    server_name example.com;

    # 主位置塊
    location / {
        # 通常的處理邏輯
        try_files $uri $uri/ =404;
    }

    # 錯誤頁面處理
    error_page 404 /404.html;
    location = /404.html {
        # 內部重定向到 @notfound 位置
        try_files @notfound;
    }

    # 定義 @notfound 內部重定向位置
    location @notfound {
        # 檢查 URL 中是否包含 /api/
        if ($request_uri ~* /api/) {
            # 如果是，代理到 API 服務器
            proxy_pass http://api_server;
        }

        # 否則返回自定義的 404 頁面
        root /usr/share/nginx/html;
        internal; # 表示這個位置只能內部訪問
    }
}

```

# 【OpenResty】相關資源

#### 跟我学OpenResty(Nginx+Lua)开发目录贴

[https://jinnianshilongnian.iteye.com/category/333854](https://jinnianshilongnian.iteye.com/category/333854)

- [第一章 安裝OpenResty(Nginx+Lua)開發環境](https://www.pudn.com/news/628f8349bf399b7f351e7ee4.html)
- [第二章OpenResty(Nginx+Lua)開發入門](https://www.pudn.com/news/628f8349bf399b7f351e7ee6.html)
- 

#### Docker下的OpenResty三部曲

- [https://blog.csdn.net/boling\_cavalry/article/details/79290944](https://blog.csdn.net/boling_cavalry/article/details/79290944)
- [https://blog.csdn.net/boling\_cavalry/article/details/79292356](https://blog.csdn.net/boling_cavalry/article/details/79292356)
- [https://blog.csdn.net/boling\_cavalry/article/details/79311164](https://blog.csdn.net/boling_cavalry/article/details/79311164)

- [使用 OpenResty Docker 镜像快速搭建 Web 服务器 - 简书 (jianshu.com)](https://www.jianshu.com/p/b3b0bf529a0b)
- [agentzh 的 Nginx 教程](https://openresty.org/download/agentzh-nginx-tutorials-zhcn.html)
- [OpenResty 最佳实践](https://moonbingbing.gitbooks.io/openresty-best-practices/content/)
- [30天 Lua重拾筆記](https://ithelp.ithome.com.tw/users/20112470/ironman/3058)
- [Nginx Lua的执行阶段](https://blog.51cto.com/xikder/2331649)
- [Lua 錯誤處理 pcall &amp; xpcall](https://www.gushiciku.cn/pl/g0Z8/zh-tw)
- [lua 線上測試](https://www.lua.org/demo.html)
- [https://github.com/openresty/lua-nginx-module](https://bookstack.treemanou.com/lua-nginx-module)
- 第三方 lib
    
    [https://github.com/ledgetech/lua-resty-http/tree/master/lib/resty](https://github.com/ledgetech/lua-resty-http/tree/master/lib/resty)
    
    https://github.com/hamishforbes/lua-ffi-zlib/blob/master/lib/ffi-zlib.lua

# 【Openresty】Lua 的變數範圍

#### 只有在 init\_by\_lua\* 和 init\_worker\_by\_lua\* 階段才能定義真正的 全域變數  
  

# 【Openresty】Nginx Lua的 執行階段

nginx 執行步驟

```
1、post-read
读取请求内容阶段，nginx读取并解析完请求头之后就立即开始运行；例如模块 ngx_realip 就在 post-read 阶段注册了处理程序，它的功能是迫使 Nginx 认为当前请求的来源地址是指定的某一个请求头的值。

2、server-rewrite
server请求地址重写阶段；当ngx_rewrite模块的set配置指令直接书写在server配置块中时，基本上都是运行在server-rewrite 阶段

3、find-config
配置查找阶段，这个阶段并不支持Nginx模块注册处理程序，而是由Nginx核心来完成当前请求与location配置块之间的配对工作。


4、rewrite
location请求地址重写阶段，当ngx_rewrite指令用于location中，就是再这个阶段运行的；另外ngx_set_misc(设置md5、encode_base64等)模块的指令，还有ngx_lua模块的set_by_lua指令和rewrite_by_lua指令也在此阶段。

5、post-rewrite
请求地址重写提交阶段，当nginx完成rewrite阶段所要求的内部跳转动作，如果rewrite阶段有这个要求的话；

6、preaccess
访问权限检查准备阶段，ngx_limit_req和ngx_limit_zone在这个阶段运行，ngx_limit_req可以控制请求的访问频率，ngx_limit_zone可以控制访问的并发度；

7、access
访问权限检查阶段，标准模块ngx_access、第三方模块ngx_auth_request以及第三方模块ngx_lua的access_by_lua指令就运行在这个阶段。配置指令多是执行访问控制相关的任务，如检查用户的访问权限，检查用户的来源IP是否合法；

8、post-access
访问权限检查提交阶段；主要用于配合access阶段实现标准ngx_http_core模块提供的配置指令satisfy的功能。satisfy all(与关系),satisfy any(或关系)

9、try-files
配置项try_files处理阶段；专门用于实现标准配置指令try_files的功能,如果前 N-1 个参数所对应的文件系统对象都不存在，try-files 阶段就会立即发起“内部跳转”到最后一个参数(即第 N 个参数)所指定的URI.

10、content
内容产生阶段，是所有请求处理阶段中最为重要的阶段，因为这个阶段的指令通常是用来生成HTTP响应内容并输出 HTTP 响应的使命；

11、log
日志模块处理阶段；记录日志
```

```Nginx
#lua 模組使用 nginx 區塊
init_by_lua            http
set_by_lua             server, server if, location, location if
rewrite_by_lua         http, server, location, location if
access_by_lua          http, server, location, location if
content_by_lua         location, location if
header_filter_by_lua   http, server, location, location if
body_filter_by_lua     http, server, location, location if
log_by_lua             http, server, location, location if
timer
```

```Nginx
#config 區塊 基本配置
...                   # 全域性區塊
 
event{                # events 區塊
        ...
}
http{                 # http 區塊
  
    init_by_lua_block{
    }
    
    init_by_lua_file /usr/local/nginx/conf/lua/init.lua
    
    #常見的功能是執行定時任務 or health_check
    #此階段一般用來進行權限檢查和黑白名單配置
    init_worker_by_lua_block{
  
  	}
  
    #配置環境：**http，server，location，location if
  	access_by_lua_block{}
  
  	#配置環境**http，server，location，location if#
    #常用於header進行添加、刪除等操作
    header_filter_by_lua_block{}
    
    server{           # server 區塊
        location /aaa {     # location 區塊
        	set $b '';
      		#  設定變量
            set_by_lua_block $a {
                local t = 'test'
                ngx.var.b = 'test_b'
                return t
            }
            return 200 $a,$b; # test,test_b
            }
    
        location /bbb {
          set $b  '1';
      	  #rewrite_by_lua_block始終都在rewrite階段的後面執行
          rewrite_by_lua_block { ngx.var.b = '2'}
          set $b  '3';
            echo $b; #2
          }
        }
  		
  		location /ccc{
    		#**配置環境：**location，location if
    		#content_by_lua_block指令不可以和其他內容處理階段的模塊如echo、return、proxy_pass等放在一起
    		content_by_lua_block{}
  		}
  
        location /ddd{
            #content_by_lua_file可以直接获取URL中参数file_name的值
            content_by_lua_file conf/lua/$arg_file_name;
        }
  
        location /eee{
    		#将响应体全部转换为大写
    		#**配置環境：**http，server，location，location if
    		body_filter_by_lua_block { ngx.arg[1] = string.upper(ngx. arg[1]) }
        }
    }
}
```

https://blog.51cto.com/xikder/2331649

# Nginx 反向代理

```INI
server {
    listen       80;
    listen  [::]:80;
    server_name  www.srou.com;

    access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        # proxy_pass http://10.2.99.14:8081;
        index  index.html index.htm;
    }

    location /dev/ {
        proxy_pass http://10.2.99.14:8081;    
    }


    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

server {
    listen       80;
    listen  [::]:80;
    server_name  wsf.srou.com;

    location / {
        # root   /usr/share/nginx/html;
        proxy_pass http://10.2.99.14:8081;
        index  index.html index.htm;
    }

    location /uat/ {
        proxy_pass http://10.2.99.14:8082;    
    }

}

server {
    listen       80;
    listen  [::]:80;
    server_name  wsm.srou.com;

    location / {
        # root   /usr/share/nginx/html;
        proxy_pass http://10.2.99.14:8082;
        index  index.html index.htm;
    }
}


```

---

### 使用 IP 來替代域名的配置方法

1. **查找域名對應的 IP 地址**：確保你知道 `a.aaa.com` 對應的 IP 地址。
2. **修改 Nginx 配置**：將原本指向域名的 `proxy_pass` 修改為指向 IP 地址。

### 具體操作步驟：

假設 `aaa.aaa.com` 對應的 IP 地址是 `192.168.1.1`，這樣配置：

```Nginx
location / {
    proxy_pass https://192.168.1.1;
    proxy_ssl_name a.aaa.com;  # 設置 SSL 握手時使用的域名
    proxy_ssl_server_name on;    # 開啟 SNI 支持
}

```

### 配置解釋：

1. **`proxy_pass` 使用 IP 地址**：直接使用 IP 地址（如 `https://192.168.1.1`）來替代域名，這樣就不需要 DNS 解析。
2. **`proxy_ssl_name` 指令**：即使使用了 IP 地址，你仍然需要指示 Nginx 在 SSL 握手過程中使用哪個主機名。這是因為 SSL/TLS 握手過程通常依賴於域名，特別是在有 SNI（Server Name Indication）需求的情況下。
3. **`proxy_ssl_server_name on` 指令**：這個指令開啟 SNI 支持，以便在 SSL 握手時能夠發送正確的域名信息給後端服務器。

### 不使用 DNS 的注意事項

- 如果你不使用 DNS 而選擇 IP 地址，一旦服務器 IP 發生變化，你需要手動更新 Nginx 配置中的 IP 地址。
- 使用 IP 地址的配置需要確保 IP 對應的服務器上有正確的 SSL 憑證，並且該憑證包含了你在 `proxy_ssl_name` 指令中指定的域名。

這種方法有效避免了 Nginx 對 DNS 解析的依賴，但需要確保 IP 地址和 SSL 配置的正確性。

# TWCA 憑證放入 Nginx

<div id="bkmrk-"></div>### <span style="font-family: Verdana, sans-serif;">憑證安裝</span>

---

<span style="font-family: Verdana, sans-serif;">TWCA發回來憑證內容若要裝在nginx 上處理方式</span>  
<span style="font-family: Verdana, sans-serif;">解開cert.zip後會有下列檔案</span>  
<span style="font-family: Verdana, sans-serif;">  
</span> <span style="font-family: Verdana, sans-serif;">主機憑證：root.cer</span>  
<span style="font-family: Verdana, sans-serif;">網域憑證：server.cer</span>  
<span style="font-family: Verdana, sans-serif;">中繼憑證1：uca\_1.cer</span>  
<span style="font-family: Verdana, sans-serif;">中繼憑證2：uca\_2.cer</span>  
<span style="font-family: Verdana, sans-serif;">  
</span> <span style="font-family: Verdana, sans-serif;">檔案內容由上而下的順序uca\_2在上面，再來是uca\_1，透過下列指令産生檔案</span>  
<span style="background-color: white; font-size: 16px; line-height: 20px;"><span style="font-family: Verdana, sans-serif;">**cp uca\_2.cer uca.crt ; cat uca\_1.cer &gt;&gt; uca.cer**</span></span>  
<span style="font-family: Verdana, sans-serif;"><span style="background-color: white; line-height: 20px;">這個個設定可以適用於apache，apache的設定如下：</span></span>  
<span style="font-family: Verdana, sans-serif;"><span style="line-height: 20px;"> SSLEngine On</span></span>  
<span style="font-family: Verdana, sans-serif;"><span style="line-height: 20px;"> SSLCertificateFile /etc/ssl/server.cer</span></span>  
<span style="font-family: Verdana, sans-serif;"><span style="line-height: 20px;"> SSLCertificateKeyFile /</span><span style="line-height: 20px;">etc/ssl</span><span style="line-height: 20px;">/server.key</span></span>  
  
<span style="font-family: Verdana, sans-serif;"><span style="line-height: 20px;"> SSLCertificateChainFile <span style="color: red;">/</span></span><span style="color: red;"><span style="line-height: 20px;">etc/ssl</span><span style="line-height: 20px;">/uca.cer</span></span></span>  
**<span style="font-family: Verdana, sans-serif;">  
</span>** <span style="background-color: white; font-size: 16px; line-height: 20px;"><span style="font-family: Verdana, sans-serif;">若你要放在nginx上的處理方式有稍梢不同</span></span>  
<span style="font-family: Verdana, sans-serif;">nginx 上的設檔如下：</span>  
<span style="font-family: Verdana, sans-serif;"> ssl on;</span>  
<span style="font-family: Verdana, sans-serif;"> ssl\_certificate <span style="color: red;">/etc/ssl/server.pem;</span></span>  
<span style="font-family: Verdana, sans-serif;"> ssl\_certificate\_key /etc/ssl/server.key;</span>  
<span style="font-family: Verdana, sans-serif;"> ssl\_session\_timeout 5m;</span>  
<span style="font-family: Verdana, sans-serif;"> ssl\_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;</span>  
<span style="font-family: Verdana, sans-serif;"> ssl\_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES";</span>  
<span style="font-family: Verdana, sans-serif;"> ssl\_prefer\_server\_ciphers on;</span>

<div id="bkmrk--1"></div>```Nginx
server{
    listen       80;
    listen       443 ssl;
    server_name  demo.server.com.tw;

    
    ssl_certificate /etc/nginx/ssl/server.pem;
    ssl_certificate_key /etc/nginx/ssl/server.key;
    ssl_session_timeout 5m;
    ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES";
    ssl_prefer_server_ciphers on;
    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        # root   /usr/share/nginx/html;
        # index  index.html index.htm;    
        proxy_http_version 1.1;
    
    	# 以下是reverse proxy 才需要
		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection "upgrade";
		proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
		proxy_set_header Host $host;
		proxy_read_timeout 60s;        
        proxy_pass   http://192.168.1.1:20599/;


    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

}
```

<div id="bkmrk-%E5%85%B6%E4%B8%AD%2Fetc%2Fssl%2Fserver.pe"><span style="font-family: Verdana, sans-serif;">  
</span> <span style="font-family: Verdana, sans-serif;">其中/etc/ssl/server.pem和原來的産生的規則有些不同，他的順序是 server.cer + uca\_2.cer + uca\_1.cer</span>  
<span style="font-family: Verdana, sans-serif;">産生方法如下</span>  
<span style="font-family: Verdana, sans-serif;">**cp server.cer server.pem;cat uca\_2.cer &gt;&gt; server.pem ; cat uca\_1.cer &gt;&gt; server.pem**</span></div><div id="bkmrk--2"></div><div id="bkmrk-https%3A%2F%2Fdavidelin.bl">**<a>https://davidelin.blogspot.com/2014/12/nginx-twca.html</a>**</div><div id="bkmrk--3"><div>---

</div></div>### 憑證需要密碼解決方案

今天設定客戶的域名遇到 `SSL_CTX_use_PrivateKey_file`、`EVP_DecryptFinal_ex:bad decrypt error` 的錯誤訊息 指出是私鑰的問題。

<div id="bkmrk-may-07-16%3A43%3A18-terr"><div><div><div>```
May 07 16:43:18 terrylin nginx[5307]: nginx: [emerg] SSL_CTX_use_PrivateKey_file("/etc/nginx/ssl/terryl.in/2020/server.key") failed (SSL: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt error:0May 07 16:43:18 terrylin nginx[5307]: nginx: configuration file /etc/nginx/nginx.conf test failed
May 07 16:43:18 terrylin systemd[1]: nginx.service: Control process exited, code=exited status=1
May 07 16:43:18 terrylin systemd[1]: nginx.service: Failed with result 'exit-code'.
May 07 16:43:18 terrylin systemd[1]: Failed to start A high performance web server and a reverse proxy server.
-- Subject: Unit nginx.service has failed
```

</div></div></div></div>打開私鑰檔案，發現在以往的長的不一樣，開頭多了一些編碼資訊。

<div id="bkmrk------begin-rsa-priva"><div><div><div>```
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,D5F9600F17CDAB82
```

</div></div></div></div>這是被密碼保護的私鑰。所以解決方法有兩種方式，一是補上設定在 nginx 的設定檔中。舉例，我們建立一個文字檔，命名為 `ssl_password.txt` 並將密碼存放在這個檔案。

在 Nginx 的設定中補上這一行。

<div id="bkmrk-ssl_password_file-%2Fe"><div><div><div>```
ssl_password_file /etc/nginx/ssl/terryl.in/2020/ssl_password.txt;
```

</div></div></div></div>重啟 Nginx，就可以了。

另外一種方法則是把有密碼保護的私鑰檔案轉成不需要密碼的私鑰。利用 OpenSSL 的指令如下：

<div id="bkmrk-openssl-rsa--in-serv"><div><div><div>```
openssl rsa -in server.key -out new_server.key
```

</div></div></div></div>會提示需要您輸入密碼。

<div id="bkmrk-enter-pass-phrase-fo"><div><div><div>```
Enter pass phrase for server.key:
```

</div></div></div></div>密碼正確輸入的話，則會匯出 `new_server.key`，這是不需要密碼保護的私鑰檔案。當然 Nginx 的設定就照之前的設定就可以了。

# 【Lua】錯誤處理

```Lua
local status, err = pcall(function()
    -- 可能会出现错误的代码
    local result = some_function()
    ngx.print("Result: ", result)
end)

if not status then
    -- 出现错误时的处理
    ngx.log(ngx.ERR, "Error occurred: ", err)
    ngx.status = 500
    ngx.print("An error occurred. Please try again later.")
end

```

# 【 Lua】動態參數

```Lua
function mylog(...)
	local arr = {...}
  	-- table.inset(table,index,value)
	table.insert(arr,1,"start")
	table.insert(arr,"end")
	for key,value in pairs(arr) do
		ngx.say(key..":"..value)
		-- ngx.say(value)
	end	
end

mylog("A","B","C","D")
-- 1:start
-- 2:A
-- 3:B
-- 4:C
-- 5:D
-- 6:end
```

# 【Lua】使用map 做全域變數

```Nginx
http {
    map $server_name $redis_server {
        default 10.10.10.1;
        example.com 10.10.10.2;  # 每個 server_name 可以有自己的 redis 伺服器
        example.org 10.10.10.3;
        # 添加更多的 server_name 和對應的 redis 伺服器
    }

    server {
    	listen 80 backlog=16384;
        server_name  example.com;
        charset UTF-8;
    
    	# $redis_server 10.10.10.2 
        set $REDIS_SERVER $redis_server;
        lua_need_request_body on;
        add_header X-Content-Length $response_body_size always;

        # 其他設定...
    }

     server {
    	listen 80 backlog=16384;
        server_name  example.org;
        charset UTF-8;
    
    	# $redis_server 10.10.10.3
        set $REDIS_SERVER $redis_server;
        lua_need_request_body on;
        add_header X-Content-Length $response_body_size always;

        # 其他設定...
    }
  
    server {
    	listen 80 backlog=16384;
        server_name  test.com;
        charset UTF-8;
    
    	# $redis_server 走預設 10.10.10.1
        set $REDIS_SERVER $redis_server;
        lua_need_request_body on;
        add_header X-Content-Length $response_body_size always;

        # 其他設定...
    }
    # 其他 server 區塊...
}

```

a

# 【Lua】Learn Lua in 15 Minutes

```Lua
Learn Lua in 15 Minutes
-- Two dashes start a one-line comment.

--[[
     Adding two ['s and ]'s makes it a
     multi-line comment.
--]]

----------------------------------------------------
-- 1. Variables and flow control.
----------------------------------------------------

num = 42  -- All numbers are doubles.
-- Don't freak out, 64-bit doubles have 52 bits for
-- storing exact int values; machine precision is
-- not a problem for ints that need < 52 bits.

s = 'walternate'  -- Immutable strings like Python.
t = "double-quotes are also fine"
u = [[ Double brackets
       start and end
       multi-line strings.]]
t = nil  -- Undefines t; Lua has garbage collection.

-- Blocks are denoted with keywords like do/end:
while num < 50 do
  num = num + 1  -- No ++ or += type operators.
end

-- If clauses:
if num > 40 then
  print('over 40')
elseif s ~= 'walternate' then  -- ~= is not equals.
  -- Equality check is == like Python; ok for strs.
  io.write('not over 40\n')  -- Defaults to stdout.
else
  -- Variables are global by default.
  thisIsGlobal = 5  -- Camel case is common.

  -- How to make a variable local:
  local line = io.read()  -- Reads next stdin line.

  -- String concatenation uses the .. operator:
  print('Winter is coming, ' .. line)
end

-- Undefined variables return nil.
-- This is not an error:
foo = anUnknownVariable  -- Now foo = nil.

aBoolValue = false

-- Only nil and false are falsy; 0 and '' are true!
if not aBoolValue then print('twas false') end

-- 'or' and 'and' are short-circuited.
-- This is similar to the a?b:c operator in C/js:
ans = aBoolValue and 'yes' or 'no'  --> 'no'

karlSum = 0
for i = 1, 100 do  -- The range includes both ends.
  karlSum = karlSum + i
end

-- Use "100, 1, -1" as the range to count down:
fredSum = 0
for j = 100, 1, -1 do fredSum = fredSum + j end

-- In general, the range is begin, end[, step].

-- Another loop construct:
repeat
  print('the way of the future')
  num = num - 1
until num == 0


----------------------------------------------------
-- 2. Functions.
----------------------------------------------------

function fib(n)
  if n < 2 then return 1 end
  return fib(n - 2) + fib(n - 1)
end

-- Closures and anonymous functions are ok:
function adder(x)
  -- The returned function is created when adder is
  -- called, and remembers the value of x:
  return function (y) return x + y end
end
a1 = adder(9)
a2 = adder(36)
print(a1(16))  --> 25
print(a2(64))  --> 100

-- Returns, func calls, and assignments all work
-- with lists that may be mismatched in length.
-- Unmatched receivers are nil;
-- unmatched senders are discarded.

x, y, z = 1, 2, 3, 4
-- Now x = 1, y = 2, z = 3, and 4 is thrown away.

function bar(a, b, c)
  print(a, b, c)
  return 4, 8, 15, 16, 23, 42
end

x, y = bar('zaphod')  --> prints "zaphod  nil nil"
-- Now x = 4, y = 8, values 15..42 are discarded.

-- Functions are first-class, may be local/global.
-- These are the same:
function f(x) return x * x end
f = function (x) return x * x end

-- And so are these:
local function g(x) return math.sin(x) end
local g; g  = function (x) return math.sin(x) end
-- the 'local g' decl makes g-self-references ok.

-- Trig funcs work in radians, by the way.

-- Calls with one string param don't need parens:
print 'hello'  -- Works fine.


----------------------------------------------------
-- 3. Tables.
----------------------------------------------------

-- Tables = Lua's only compound data structure;
--          they are associative arrays.
-- Similar to php arrays or js objects, they are
-- hash-lookup dicts that can also be used as lists.

-- Using tables as dictionaries / maps:

-- Dict literals have string keys by default:
t = {key1 = 'value1', key2 = false}

-- String keys can use js-like dot notation:
print(t.key1)  -- Prints 'value1'.
t.newKey = {}  -- Adds a new key/value pair.
t.key2 = nil   -- Removes key2 from the table.

-- Literal notation for any (non-nil) value as key:
u = {['@!#'] = 'qbert', [{}] = 1729, [6.28] = 'tau'}
print(u[6.28])  -- prints "tau"

-- Key matching is basically by value for numbers
-- and strings, but by identity for tables.
a = u['@!#']  -- Now a = 'qbert'.
b = u[{}]     -- We might expect 1729, but it's nil:
-- b = nil since the lookup fails. It fails
-- because the key we used is not the same object
-- as the one used to store the original value. So
-- strings & numbers are more portable keys.

-- A one-table-param function call needs no parens:
function h(x) print(x.key1) end
h{key1 = 'Sonmi~451'}  -- Prints 'Sonmi~451'.

for key, val in pairs(u) do  -- Table iteration.
  print(key, val)
end

-- _G is a special table of all globals.
print(_G['_G'] == _G)  -- Prints 'true'.

-- Using tables as lists / arrays:

-- List literals implicitly set up int keys:
v = {'value1', 'value2', 1.21, 'gigawatts'}
for i = 1, #v do  -- #v is the size of v for lists.
  print(v[i])  -- Indices start at 1 !! SO CRAZY!
end
-- A 'list' is not a real type. v is just a table
-- with consecutive integer keys, treated as a list.

----------------------------------------------------
-- 3.1 Metatables and metamethods.
----------------------------------------------------

-- A table can have a metatable that gives the table
-- operator-overloadish behavior. Later we'll see
-- how metatables support js-prototypey behavior.

f1 = {a = 1, b = 2}  -- Represents the fraction a/b.
f2 = {a = 2, b = 3}

-- This would fail:
-- s = f1 + f2

metafraction = {}
function metafraction.__add(f1, f2)
  sum = {}
  sum.b = f1.b * f2.b
  sum.a = f1.a * f2.b + f2.a * f1.b
  return sum
end

setmetatable(f1, metafraction)
setmetatable(f2, metafraction)

s = f1 + f2  -- call __add(f1, f2) on f1's metatable

-- f1, f2 have no key for their metatable, unlike
-- prototypes in js, so you must retrieve it as in
-- getmetatable(f1). The metatable is a normal table
-- with keys that Lua knows about, like __add.

-- But the next line fails since s has no metatable:
-- t = s + s
-- Class-like patterns given below would fix this.

-- An __index on a metatable overloads dot lookups:
defaultFavs = {animal = 'gru', food = 'donuts'}
myFavs = {food = 'pizza'}
setmetatable(myFavs, {__index = defaultFavs})
eatenBy = myFavs.animal  -- works! thanks, metatable

-- Direct table lookups that fail will retry using
-- the metatable's __index value, and this recurses.

-- An __index value can also be a function(tbl, key)
-- for more customized lookups.

-- Values of __index,add, .. are called metamethods.
-- Full list. Here a is a table with the metamethod.

-- __add(a, b)                     for a + b
-- __sub(a, b)                     for a - b
-- __mul(a, b)                     for a * b
-- __div(a, b)                     for a / b
-- __mod(a, b)                     for a % b
-- __pow(a, b)                     for a ^ b
-- __unm(a)                        for -a
-- __concat(a, b)                  for a .. b
-- __len(a)                        for #a
-- __eq(a, b)                      for a == b
-- __lt(a, b)                      for a < b
-- __le(a, b)                      for a <= b
-- __index(a, b)  <fn or a table>  for a.b
-- __newindex(a, b, c)             for a.b = c
-- __call(a, ...)                  for a(...)

----------------------------------------------------
-- 3.2 Class-like tables and inheritance.
----------------------------------------------------

-- Classes aren't built in; there are different ways
-- to make them using tables and metatables.

-- Explanation for this example is below it.

Dog = {}                                   -- 1.

function Dog:new()                         -- 2.
  newObj = {sound = 'woof'}                -- 3.
  self.__index = self                      -- 4.
  return setmetatable(newObj, self)        -- 5.
end

function Dog:makeSound()                   -- 6.
  print('I say ' .. self.sound)
end

mrDog = Dog:new()                          -- 7.
mrDog:makeSound()  -- 'I say woof'         -- 8.

-- 1. Dog acts like a class; it's really a table.
-- 2. function tablename:fn(...) is the same as
--    function tablename.fn(self, ...)
--    The : just adds a first arg called self.
--    Read 7 & 8 below for how self gets its value.
-- 3. newObj will be an instance of class Dog.
-- 4. self = the class being instantiated. Often
--    self = Dog, but inheritance can change it.
--    newObj gets self's functions when we set both
--    newObj's metatable and self's __index to self.
-- 5. Reminder: setmetatable returns its first arg.
-- 6. The : works as in 2, but this time we expect
--    self to be an instance instead of a class.
-- 7. Same as Dog.new(Dog), so self = Dog in new().
-- 8. Same as mrDog.makeSound(mrDog); self = mrDog.

----------------------------------------------------

-- Inheritance example:

LoudDog = Dog:new()                           -- 1.

function LoudDog:makeSound()
  s = self.sound .. ' '                       -- 2.
  print(s .. s .. s)
end

seymour = LoudDog:new()                       -- 3.
seymour:makeSound()  -- 'woof woof woof'      -- 4.

-- 1. LoudDog gets Dog's methods and variables.
-- 2. self has a 'sound' key from new(), see 3.
-- 3. Same as LoudDog.new(LoudDog), and converted to
--    Dog.new(LoudDog) as LoudDog has no 'new' key,
--    but does have __index = Dog on its metatable.
--    Result: seymour's metatable is LoudDog, and
--    LoudDog.__index = LoudDog. So seymour.key will
--    = seymour.key, LoudDog.key, Dog.key, whichever
--    table is the first with the given key.
-- 4. The 'makeSound' key is found in LoudDog; this
--    is the same as LoudDog.makeSound(seymour).

-- If needed, a subclass's new() is like the base's:
function LoudDog:new()
  newObj = {}
  -- set up newObj
  self.__index = self
  return setmetatable(newObj, self)
end

----------------------------------------------------
-- 4. Modules.
----------------------------------------------------


--[[ I'm commenting out this section so the rest of
--   this script remains runnable.
-- Suppose the file mod.lua looks like this:
local M = {}

local function sayMyName()
  print('Hrunkner')
end

function M.sayHello()
  print('Why hello there')
  sayMyName()
end

return M

-- Another file can use mod.lua's functionality:
local mod = require('mod')  -- Run the file mod.lua.

-- require is the standard way to include modules.
-- require acts like:     (if not cached; see below)
local mod = (function ()
  <contents of mod.lua>
end)()
-- It's like mod.lua is a function body, so that
-- locals inside mod.lua are invisible outside it.

-- This works because mod here = M in mod.lua:
mod.sayHello()  -- Says hello to Hrunkner.

-- This is wrong; sayMyName only exists in mod.lua:
mod.sayMyName()  -- error

-- require's return values are cached so a file is
-- run at most once, even when require'd many times.

-- Suppose mod2.lua contains "print('Hi!')".
local a = require('mod2')  -- Prints Hi!
local b = require('mod2')  -- Doesn't print; a=b.

-- dofile is like require without caching:
dofile('mod2.lua')  --> Hi!
dofile('mod2.lua')  --> Hi! (runs it again)

-- loadfile loads a lua file but doesn't run it yet.
f = loadfile('mod2.lua')  -- Call f() to run it.

-- loadstring is loadfile for strings.
g = loadstring('print(343)')  -- Returns a function.
g()  -- Prints out 343; nothing printed before now.

--]]

----------------------------------------------------
-- 5. References.
----------------------------------------------------

--[[

I was excited to learn Lua so I could make games
with the Löve 2D game engine. That's the why.

I started with BlackBulletIV's Lua for programmers.
Next I read the official Programming in Lua book.
That's the how.

It might be helpful to check out the Lua short
reference on lua-users.org.

The main topics not covered are standard libraries:
 * string library
 * table library
 * math library
 * io library
 * os library

By the way, this entire file is valid Lua; save it
as learn.lua and run it with "lua learn.lua" !

This was first written for tylerneylon.com. It's
also available as a github gist. Tutorials for other
languages, in the same style as this one, are here:

https://learnxinyminutes.com/

Have fun with Lua!

--]]

Tyler Neylon
336.2013
```

# Node.js

# 【debug】centos7

## 場景

centos7服務器使用nvm安裝的node之後，只要使用npm或者node，均會出現以下問題

<div id="bkmrk-https%3A%2F%2Fwww.cnblogs.">[https://www.cnblogs.com/dingshaohua/p/17103654.html](https://www.cnblogs.com/dingshaohua/p/17103654.html)</div><div data-lang="highlighter-hljs shell" id="bkmrk-%5Broot%40172-%7E%5D%23-npm--v"><textarea style="display: none;">\[root@172 ~\]# npm -v node: /lib64/libm.so.6: version `GLIBC\_2.27' not found (required by node) node: /lib64/libc.so.6: version `GLIBC\_2.25' not found (required by node) node: /lib64/libc.so.6: version `GLIBC\_2.28' not found (required by node) node: /lib64/libstdc++.so.6: version `CXXABI\_1.3.9' not found (required by node) node: /lib64/libstdc++.so.6: version `GLIBCXX\_3.4.20' not found (required by node) node: /lib64/libstdc++.so.6: version `GLIBCXX\_3.4.21' not found (required by node) </textarea><div><div><textarea readonly="readonly" spellcheck="false" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;" tabindex="0"></textarea></div><div><div></div></div><div><div></div></div><div></div><div></div><div><div><div><div><div><div>```
xxxxxxxxxx
```

</div><div></div><div></div><div><div> </div></div><div><div><div aria-hidden="true"><div>1</div></div>```
<span role="presentation" style="padding-right: 0.1px;">[root@172 ~]# npm -v</span>
```

</div><div><div aria-hidden="true"><div>2</div></div>```
<span role="presentation" style="padding-right: 0.1px;">node: /lib64/libm.so.6: version `GLIBC_2.27' not found (required by node)</span>
```

</div><div><div aria-hidden="true"><div>3</div></div>```
<span role="presentation" style="padding-right: 0.1px;">node: /lib64/libc.so.6: version `GLIBC_2.25' not found (required by node)</span>
```

</div><div><div aria-hidden="true"><div>4</div></div>```
<span role="presentation" style="padding-right: 0.1px;">node: /lib64/libc.so.6: version `GLIBC_2.28' not found (required by node)</span>
```

</div><div><div aria-hidden="true"><div>5</div></div>```
<span role="presentation" style="padding-right: 0.1px;">node: /lib64/libstdc++.so.6: version `CXXABI_1.3.9' not found (required by node)</span>
```

</div><div><div aria-hidden="true"><div>6</div></div>```
<span role="presentation" style="padding-right: 0.1px;">node: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.20' not found (required by node)</span>
```

</div><div><div aria-hidden="true"><div>7</div></div>```
<span role="presentation" style="padding-right: 0.1px;">node: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by node)</span>
```

</div><div><div aria-hidden="true"><div>8</div></div>```
<span role="presentation" style="padding-right: 0.1px;">​</span>
```

</div></div></div></div></div></div><div></div><div><div></div></div></div></div></div>## 原因

查看系統內安裝的glibc版本  
然後再根據分析可得知新版的node v18開始都需要GLIBC\_2.27支持，可是目前系統內卻沒有那麼高的版本

<div id="bkmrk-"></div><div data-lang="highlighter-hljs shell" id="bkmrk-%5Broot%40172-glibc-2.28"><textarea style="display: none;">\[root@172 glibc-2.28\]# strings /lib64/libc.so.6 |grep GLIBC\_ GLIBC\_2.2.5 ... GLIBC\_2.17 .... </textarea><div><div><textarea readonly="readonly" spellcheck="false" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;" tabindex="0"></textarea></div><div><div></div></div><div><div></div></div><div></div><div></div><div><div><div><div><div><div>```
xxxxxxxxxx
```

</div><div></div><div></div><div><div> </div></div><div><div><div aria-hidden="true"><div>1</div></div>```
<span role="presentation" style="padding-right: 0.1px;">[root@172 glibc-2.28]# strings /lib64/libc.so.6 |grep GLIBC_</span>
```

</div><div><div aria-hidden="true"><div>2</div></div>```
<span role="presentation" style="padding-right: 0.1px;">GLIBC_2.2.5</span>
```

</div><div><div aria-hidden="true"><div>3</div></div>```
<span role="presentation" style="padding-right: 0.1px;">...</span>
```

</div><div><div aria-hidden="true"><div>4</div></div>```
<span role="presentation" style="padding-right: 0.1px;">GLIBC_2.17</span>
```

</div><div><div aria-hidden="true"><div>5</div></div>```
<span role="presentation" style="padding-right: 0.1px;">....</span>
```

</div><div><div aria-hidden="true"><div>6</div></div>```
<span role="presentation" style="padding-right: 0.1px;">​</span>
```

</div></div></div></div></div></div><div></div><div><div></div></div></div></div></div>## 解決辦法

### 更新glibc

根據提示安裝所需要的glibc-2.28

<div id="bkmrk--0"></div><div data-lang="highlighter-hljs shell" id="bkmrk-wget-http%3A%2F%2Fftp.gnu."><textarea style="display: none;">wget http://ftp.gnu.org/gnu/glibc/glibc-2.28.tar.gz tar xf glibc-2.28.tar.gz cd glibc-2.28/ &amp;&amp; mkdir build &amp;&amp; cd build ../configure --prefix=/usr --disable-profile --enable-add-ons --with-headers=/usr/include --with-binutils=/usr/bin </textarea><div><div><textarea readonly="readonly" spellcheck="false" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;" tabindex="0"></textarea></div><div><div></div></div><div><div></div></div><div></div><div></div><div><div><div><div><div><div>```
xxxxxxxxxx
```

</div><div></div><div></div><div><div> </div></div><div><div><div aria-hidden="true"><div>1</div></div>```
<span role="presentation" style="padding-right: 0.1px;">wget http://ftp.gnu.org/gnu/glibc/glibc-2.28.tar.gz</span>
```

</div><div><div aria-hidden="true"><div>2</div></div>```
<span role="presentation" style="padding-right: 0.1px;">tar xf glibc-2.28.tar.gz </span>
```

</div><div><div aria-hidden="true"><div>3</div></div>```
<span role="presentation" style="padding-right: 0.1px;">cd glibc-2.28/ && mkdir build  && cd build</span>
```

</div><div><div aria-hidden="true"><div>4</div></div>```
<span role="presentation" style="padding-right: 0.1px;">../configure --prefix=/usr --disable-profile --enable-add-ons --with-headers=/usr/include --with-binutils=/usr/bin</span>
```

</div><div><div aria-hidden="true"><div>5</div></div>```
<span role="presentation" style="padding-right: 0.1px;">​</span>
```

</div></div></div></div></div></div><div></div><div><div></div></div></div></div></div>### 可能出現的錯誤

上步更新glibc 可能會發生錯誤。  
如果沒有錯誤下邊這一步不用看。

#### make問題

<div id="bkmrk--1"></div><div data-lang="highlighter-hljs shell" id="bkmrk-configure%3A-error%3A-%2A%2A"><textarea style="display: none;">configure: error: \*\*\* These critical programs are missing or too old: make bison compiler \*\*\* Check the INSTALL file for required versions. </textarea><div><div><textarea readonly="readonly" spellcheck="false" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;" tabindex="0"></textarea></div><div><div></div></div><div><div></div></div><div></div><div></div><div><div><div><div><div><div>```
xxxxxxxxxx
```

</div><div></div><div></div><div><div> </div></div><div><div><div aria-hidden="true"><div>1</div></div>```
<span role="presentation" style="padding-right: 0.1px;">configure: error: </span>
```

</div><div><div aria-hidden="true"><div>2</div></div>```
<span role="presentation" style="padding-right: 0.1px;">*** These critical programs are missing or too old: make bison compiler</span>
```

</div><div><div aria-hidden="true"><div>3</div></div>```
<span role="presentation" style="padding-right: 0.1px;">*** Check the INSTALL file for required versions.</span>
```

</div><div><div aria-hidden="true"><div>4</div></div>```
<span role="presentation" style="padding-right: 0.1px;">​</span>
```

</div></div></div></div></div></div><div></div><div><div></div></div></div></div></div>解決辦法：升級gcc與make

<div id="bkmrk--2"></div><div data-lang="highlighter-hljs shell" id="bkmrk-%23-%E5%8D%87%E7%BA%A7gcc%28%E9%BB%98%E8%AE%A4%E4%B8%BA4-%E5%8D%87%E7%BA%A7%E4%B8%BA8%29-y"><textarea style="display: none;">\# 升级GCC(默认为4 升级为8) yum install -y centos-release-scl yum install -y devtoolset-8-gcc\* mv /usr/bin/gcc /usr/bin/gcc-4.8.5 ln -s /opt/rh/devtoolset-8/root/bin/gcc /usr/bin/gcc mv /usr/bin/g++ /usr/bin/g++-4.8.5 ln -s /opt/rh/devtoolset-8/root/bin/g++ /usr/bin/g++ # 升级 make(默认为3 升级为4) wget http://ftp.gnu.org/gnu/make/make-4.3.tar.gz tar -xzvf make-4.3.tar.gz &amp;&amp; cd make-4.3/ ./configure --prefix=/usr/local/make make &amp;&amp; make install cd /usr/bin/ &amp;&amp; mv make make.bak ln -sv /usr/local/make/bin/make /usr/bin/make </textarea><div><div><textarea readonly="readonly" spellcheck="false" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;" tabindex="0"></textarea></div><div><div></div></div><div><div></div></div><div></div><div></div><div><div><div><div><div><div>```
xxxxxxxxxx
```

</div><div></div><div></div><div><div> </div></div><div><div><div aria-hidden="true"><div>1</div></div>```
<span role="presentation" style="padding-right: 0.1px;"># 升级GCC(默认为4 升级为8)</span>
```

</div><div><div aria-hidden="true"><div>2</div></div>```
<span role="presentation" style="padding-right: 0.1px;">yum install -y centos-release-scl</span>
```

</div><div><div aria-hidden="true"><div>3</div></div>```
<span role="presentation" style="padding-right: 0.1px;">yum install -y devtoolset-8-gcc*</span>
```

</div><div><div aria-hidden="true"><div>4</div></div>```
<span role="presentation" style="padding-right: 0.1px;">mv /usr/bin/gcc /usr/bin/gcc-4.8.5</span>
```

</div><div><div aria-hidden="true"><div>5</div></div>```
<span role="presentation" style="padding-right: 0.1px;">ln -s /opt/rh/devtoolset-8/root/bin/gcc /usr/bin/gcc</span>
```

</div><div><div aria-hidden="true"><div>6</div></div>```
<span role="presentation" style="padding-right: 0.1px;">mv /usr/bin/g++ /usr/bin/g++-4.8.5</span>
```

</div><div><div aria-hidden="true"><div>7</div></div>```
<span role="presentation" style="padding-right: 0.1px;">ln -s /opt/rh/devtoolset-8/root/bin/g++ /usr/bin/g++</span>
```

</div><div><div aria-hidden="true"><div>8</div></div>```
<span role="presentation" style="padding-right: 0.1px;">​</span>
```

</div><div><div aria-hidden="true"><div>9</div></div>```
<span role="presentation" style="padding-right: 0.1px;"># 升级 make(默认为3 升级为4)</span>
```

</div><div><div aria-hidden="true"><div>10</div></div>```
<span role="presentation" style="padding-right: 0.1px;">wget http://ftp.gnu.org/gnu/make/make-4.3.tar.gz</span>
```

</div><div><div aria-hidden="true"><div>11</div></div>```
<span role="presentation" style="padding-right: 0.1px;">tar -xzvf make-4.3.tar.gz && cd make-4.3/</span>
```

</div><div><div aria-hidden="true"><div>12</div></div>```
<span role="presentation" style="padding-right: 0.1px;">./configure  --prefix=/usr/local/make</span>
```

</div><div><div aria-hidden="true"><div>13</div></div>```
<span role="presentation" style="padding-right: 0.1px;">make && make install</span>
```

</div><div><div aria-hidden="true"><div>14</div></div>```
<span role="presentation" style="padding-right: 0.1px;">cd /usr/bin/ && mv make make.bak</span>
```

</div><div><div aria-hidden="true"><div>15</div></div>```
<span role="presentation" style="padding-right: 0.1px;">ln -sv /usr/local/make/bin/make /usr/bin/make</span>
```

</div><div><div aria-hidden="true"><div>16</div></div>```
<span role="presentation" style="padding-right: 0.1px;">​</span>
```

</div></div></div></div></div></div><div></div><div><div></div></div></div></div></div>這時所有的問題都已經解決完畢再重新執行上一步更新glibc即可

<div id="bkmrk--3"></div><div data-lang="highlighter-hljs shell" id="bkmrk-cd-%2Froot%2Fglibc-2.28%2F"><textarea style="display: none;">cd /root/glibc-2.28/build ../configure --prefix=/usr --disable-profile --enable-add-ons --with-headers=/usr/include --with-binutils=/usr/bin </textarea><div><div><textarea readonly="readonly" spellcheck="false" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;" tabindex="0"></textarea></div><div><div></div></div><div><div></div></div><div></div><div></div><div><div><div><div><div><div>```
xxxxxxxxxx
```

</div><div></div><div></div><div><div> </div></div><div><div><div aria-hidden="true"><div>1</div></div>```
<span role="presentation" style="padding-right: 0.1px;">cd /root/glibc-2.28/build</span>
```

</div><div><div aria-hidden="true"><div>2</div></div>```
<span role="presentation" style="padding-right: 0.1px;">../configure --prefix=/usr --disable-profile --enable-add-ons --with-headers=/usr/include --with-binutils=/usr/bin</span>
```

</div><div><div aria-hidden="true"><div>3</div></div>```
<span role="presentation" style="padding-right: 0.1px;">​</span>
```

</div></div></div></div></div></div><div></div><div><div></div></div></div></div></div>我的依舊報錯：bison太老舊

<div id="bkmrk--4"></div><div data-lang="highlighter-hljs shell" id="bkmrk-configure%3A-error%3A-%2A%2A-0"><textarea style="display: none;">configure: error: \*\*\* These critical programs are missing or too old: bison \*\*\* Check the INSTALL file for required versions. </textarea><div><div><textarea readonly="readonly" spellcheck="false" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;" tabindex="0"></textarea></div><div><div></div></div><div><div></div></div><div></div><div></div><div><div><div><div><div><div>```
xxxxxxxxxx
```

</div><div></div><div></div><div><div> </div></div><div><div><div aria-hidden="true"><div>1</div></div>```
<span role="presentation" style="padding-right: 0.1px;">configure: error: </span>
```

</div><div><div aria-hidden="true"><div>2</div></div>```
<span role="presentation" style="padding-right: 0.1px;">*** These critical programs are missing or too old: bison</span>
```

</div><div><div aria-hidden="true"><div>3</div></div>```
<span role="presentation" style="padding-right: 0.1px;">*** Check the INSTALL file for required versions.</span>
```

</div><div><div aria-hidden="true"><div>4</div></div>```
<span role="presentation" style="padding-right: 0.1px;">​</span>
```

</div></div></div></div></div></div><div></div><div><div></div></div></div></div></div>看看我的bison版本多少

<div id="bkmrk--5"></div><div data-lang="highlighter-hljs shell" id="bkmrk-%5Broot%40172-%7E%5D%23-bison-"><textarea style="display: none;">\[root@172 ~\]# bison -v -bash: bison: 未找到命令 </textarea><div><div><textarea readonly="readonly" spellcheck="false" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;" tabindex="0"></textarea></div><div><div></div></div><div><div></div></div><div></div><div></div><div><div><div><div><div><div>```
xxxxxxxxxx
```

</div><div></div><div></div><div><div> </div></div><div><div><div aria-hidden="true"><div>1</div></div>```
<span role="presentation" style="padding-right: 0.1px;">[root@172 ~]# bison -v</span>
```

</div><div><div aria-hidden="true"><div>2</div></div>```
<span role="presentation" style="padding-right: 0.1px;">-bash: bison: 未找到命令</span>
```

</div><div><div aria-hidden="true"><div>3</div></div>```
<span role="presentation" style="padding-right: 0.1px;">​</span>
```

</div></div></div></div></div></div><div></div><div><div></div></div></div></div></div>#### bison問題

嗨，沒裝啊。裝一下唄

<div id="bkmrk--6"></div><div data-lang="highlighter-hljs shell" id="bkmrk-yum-install--y-bison"><textarea style="display: none;">yum install -y bison </textarea><div><div><textarea readonly="readonly" spellcheck="false" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;" tabindex="0"></textarea></div><div><div></div></div><div><div></div></div><div></div><div></div><div><div><div><div><div><div>```
xxxxxxxxxx
```

</div><div></div><div></div><div><div> </div></div><div><div><div aria-hidden="true"><div>1</div></div>```
<span role="presentation" style="padding-right: 0.1px;">yum install -y bison</span>
```

</div><div><div aria-hidden="true"><div>2</div></div>```
<span role="presentation" style="padding-right: 0.1px;">​</span>
```

</div></div></div></div></div></div><div></div><div><div></div></div></div></div></div>這時所有的問題真的真的都已經解決完畢再重新執行上一步更新glibc即可

<div id="bkmrk--7"></div><div data-lang="highlighter-hljs shell" id="bkmrk-cd-%2Froot%2Fglibc-2.28%2F-0"><textarea style="display: none;">cd /root/glibc-2.28/build ../configure --prefix=/usr --disable-profile --enable-add-ons --with-headers=/usr/include --with-binutils=/usr/bin </textarea><div><div><textarea readonly="readonly" spellcheck="false" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;" tabindex="0"></textarea></div><div><div></div></div><div><div></div></div><div></div><div></div><div><div><div><div><div><div>```
xxxxxxxxxx
```

</div><div></div><div></div><div><div> </div></div><div><div><div aria-hidden="true"><div>1</div></div>```
<span role="presentation" style="padding-right: 0.1px;">cd /root/glibc-2.28/build</span>
```

</div><div><div aria-hidden="true"><div>2</div></div>```
<span role="presentation" style="padding-right: 0.1px;">../configure --prefix=/usr --disable-profile --enable-add-ons --with-headers=/usr/include --with-binutils=/usr/bin</span>
```

</div><div><div aria-hidden="true"><div>3</div></div>```
<span role="presentation" style="padding-right: 0.1px;">​</span>
```

</div></div></div></div></div></div><div></div><div><div></div></div></div></div></div>### 繼續更新glibc

make 和make install在linux中就是安裝軟件的意思簡單這麼理解就好。  
這個過程較長，大約半小時左右，建議打一局遊戲就好了。

<div id="bkmrk--8"></div><div data-lang="highlighter-hljs shell" id="bkmrk-make-%26%26-make-install"><textarea style="display: none;">make &amp;&amp; make install `` 验证下 是不是好了 ```shell npm -v </textarea><div><div><textarea readonly="readonly" spellcheck="false" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;" tabindex="0"></textarea></div><div><div></div></div><div><div></div></div><div></div><div></div><div><div><div><div><div><div>```
xxxxxxxxxx
```

</div><div></div><div></div><div><div> </div></div><div><div><div aria-hidden="true"><div>1</div></div>```
<span role="presentation" style="padding-right: 0.1px;">make && make install</span>
```

</div><div><div aria-hidden="true"><div>2</div></div>```
<span role="presentation" style="padding-right: 0.1px;">``</span>
```

</div><div><div aria-hidden="true"><div>3</div></div>```
<span role="presentation" style="padding-right: 0.1px;">​</span>
```

</div><div><div aria-hidden="true"><div>4</div></div>```
<span role="presentation" style="padding-right: 0.1px;">验证下 是不是好了</span>
```

</div><div><div aria-hidden="true"><div>5</div></div>```
<span role="presentation" style="padding-right: 0.1px;">```shell</span>
```

</div><div><div aria-hidden="true"><div>6</div></div>```
<span role="presentation" style="padding-right: 0.1px;">npm -v</span>
```

</div><div><div aria-hidden="true"><div>7</div></div>```
<span role="presentation" style="padding-right: 0.1px;">​</span>
```

</div></div></div></div></div></div><div></div><div><div></div></div></div></div></div><div id="bkmrk-%E5%A0%B4%E6%99%AF-%E5%8E%9F%E5%9B%A0-%E8%A7%A3%E6%B1%BA%E8%BE%A6%E6%B3%95-%C2%A0%C2%A0%C2%A0%C2%A0%E6%9B%B4%E6%96%B0gli"><nav class="articleDirectoryFixed" id="bkmrk-%E5%A0%B4%E6%99%AF%E5%8E%9F%E5%9B%A0%E8%A7%A3%E6%B1%BA%E8%BE%A6%E6%B3%95%C2%A0%C2%A0%C2%A0%C2%A0%E6%9B%B4%E6%96%B0glibc%C2%A0-0">- [場景](https://www.cnblogs.com/dingshaohua/p/17103654.html#%E5%9C%BA%E6%99%AF)
- [原因](https://www.cnblogs.com/dingshaohua/p/17103654.html#%E5%8E%9F%E5%9B%A0)
- [解決辦法](https://www.cnblogs.com/dingshaohua/p/17103654.html#%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95)
- [ 更新glibc](https://www.cnblogs.com/dingshaohua/p/17103654.html#%E6%9B%B4%E6%96%B0glibc)
- [ 可能出現的錯誤](https://www.cnblogs.com/dingshaohua/p/17103654.html#%E5%8F%AF%E8%83%BD%E5%87%BA%E7%8E%B0%E7%9A%84%E9%94%99%E8%AF%AF)
- [ make問題](https://www.cnblogs.com/dingshaohua/p/17103654.html#make%E9%97%AE%E9%A2%98)
- [ bison問題](https://www.cnblogs.com/dingshaohua/p/17103654.html#bison%E9%97%AE%E9%A2%98)
- [ 繼續更新glibc](https://www.cnblogs.com/dingshaohua/p/17103654.html#%E7%BB%A7%E7%BB%AD%E6%9B%B4%E6%96%B0glibc)

</nav></div>

# 【npm】js 套件管理工具

```shell
# 安裝套件
npm install 套件名

```

[![image-1639310847802.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/NOJ9PUan4VklXwWz-image-1639310847802.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/NOJ9PUan4VklXwWz-image-1639310847802.png)

node\_modules =&gt; 套件檔案放置處

public =&gt; 打包後的進入點

src =&gt; 原始檔

package-lock.json 目前套件版本安裝詳細資訊

package.json =&gt; npm 設定檔

[![image-1639311096093.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/H3Rln5ksad0iA8G8-image-1639311096093.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/H3Rln5ksad0iA8G8-image-1639311096093.png)

script : npm 指令

ex. npm run dev =&gt; npm run development

# 【nvm】Node.js 管理工具

- #### nvm

[https://github.com/nvm-sh/nvm](https://github.com/nvm-sh/nvm)

[https://github.com/coreybutler/nvm-windows ](https://github.com/coreybutler/nvm-windows)(windows 安裝) 如果安裝前有安裝node.js 請先移除 [【windows 移除 node.js】](https://bookstack.treemanou.com/books/treeman/page/nodejs-windows-nodejs)

[![image-1639308909063.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/gAALUONYlLhFPyBK-image-1639308909063.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/gAALUONYlLhFPyBK-image-1639308909063.png)

### Mac 安裝nvm

```shell
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
```

如果下nvm指令找不到

在以下檔案(依照你常用的套件擇一)加入以下文字

*bash*: `vim ~/.bashrc`

*zsh*: `vim ~/.zshrc```

```shell
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
```

重新登入或是依使用shell則一重新讀入設定檔

*bash*: `source ~/.bashrc`

*zsh*: `source ~/.zshrc`

參考: [https://github.com/nvm-sh/nvm#installing-and-updating](https://github.com/nvm-sh/nvm#installing-and-updating)

---

### MVN 常用指令

- nvm ls-remote：列出目前可用的遠端 Node.js 版本
- nvm install ：安裝特定版本的 Node.js
- nvm ls：列出本定端所安裝的 Node.js 環境
- nvm alias default node：設定命令列預設開啟的 Node.js 版本
- nvm use：當前命令列套用特定版本的 Node.js

```shell
# 列出目前電腦有安裝nodejs版本
nvm list

# 目前網路上可用的nodejs 版本
## mac
nvm ls-remote
## windows
nvm list available

#安裝12.19.0版本
nvm install v12.19.0 

#移除12.19.0版本
nvm nuinstall v12.19.0

#使用12.19.0版本
nvm use 12.19.0

#查看目前版本
nvm -v 
```

# 【NodeJs】從 Windows 移除 Node.js：

### 🧹 如何從 Windows 移除 Node.js：

1. 執行以下指令清除 npm 快取（強制執行）：
    
    ```bash
    npm cache clean --force
    
    ```
2. 前往 **控制台 &gt; 程式與功能（Programs &amp; Features）**，使用 Node.js 的解除安裝程式移除它。
3. **重新啟動電腦**（或是也可以嘗試從工作管理員中關閉所有與 node 相關的程序）。
4. **檢查並刪除以下資料夾（若存在）**，這些資料夾的存在與否取決於你所安裝的版本、使用者帳號控制（UAC）設定，以及 CPU 架構：
    
    ```
    C:\Program Files (x86)\Nodejs
    C:\Program Files\Nodejs
    C:\Users\{你的使用者名稱}\AppData\Roaming\npm  （或使用 %appdata%\npm）
    C:\Users\{你的使用者名稱}\AppData\Roaming\npm-cache （或使用 %appdata%\npm-cache）
    C:\Users\{你的使用者名稱}\.npmrc（也建議檢查是否有沒有點開頭的 npmrc 檔）
    C:\Users\{你的使用者名稱}\AppData\Local\Temp\npm-*
    
    ```
5. 檢查你的系統環境變數 `%PATH%`，確保裡面**沒有出現與 Node.js 或 npm 相關的路徑**。
6. 若仍未完全移除，請在命令提示字元輸入：
    
    ```bash
    where node
    
    ```
    
    查看 Node.js 所在位置，然後**刪除該檔案以及其上層目錄**。
7. **建議最後再重新啟動電腦一次，以確保清乾淨。**

---

這是一個 `.bat` 批次檔範例，可協助你**自動清理 Node.js 相關資料與快取**。請依照你的實際使用者名稱調整：

---

### `uninstall_nodejs.bat`

```bat
@echo off
echo 正在清除 npm 快取...
npm cache clean --force

echo 正在嘗試終止與 node 相關的進程...
taskkill /F /IM node.exe >nul 2>&1
taskkill /F /IM npm.exe >nul 2>&1

echo 正在刪除 Node.js 相關資料夾...

REM 移除常見安裝目錄
rmdir /S /Q "C:\Program Files\nodejs"
rmdir /S /Q "C:\Program Files (x86)\nodejs"

REM 移除 npm 快取與配置檔案（將 USERNAME 改為你的使用者名稱）
rmdir /S /Q "%APPDATA%\npm"
rmdir /S /Q "%APPDATA%\npm-cache"
del /Q "%USERPROFILE%\.npmrc"
del /Q "%USERPROFILE%\npmrc"
del /Q "%USERPROFILE%\AppData\Local\Temp\npm-*"

echo 正在檢查 PATH 變數，請手動確認是否含有 node/npm 路徑。

where node
where npm

echo ===== 建議重開機以完成所有清理 =====
pause

```

---

### 🚀 使用方法

1. 將上方內容儲存為 `uninstall_nodejs.bat`
2. 右鍵 → 以系統管理員身份執行
3. 程式會幫你自動清除大部分資料夾
4. **手動確認**環境變數（Path）內無 Node/npm 相關路徑

---

是否需要我幫你自動修改環境變數？（這操作涉及系統設定，會較敏感，我可以引導你一步步執行）

# PHP

# 【php】在 CentOS 6 安裝、設定 Oracel PDO_OCI

[在 CentOS 6 安裝、設定 Oracel PDO\_OCI | 未知 (dtask.idv.tw)](https://blog.dtask.idv.tw/Centos/oracle_pdo_oci_in_centos_6/)

---

[https://www.oracle.com/database/technologies/instant-client.html](https://www.oracle.com/database/technologies/instant-client.html)

```PHP
<?php
// IP 與 PORT
$tns = '
(DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.3.1)(PORT = 1521))
    )
    (CONNECT_DATA =
        (SERVICE_NAME = orcl)
    )
)';

$user = 'tnfd';     // 帳號
$passwd = 'tnfd';   // 密碼

try {
    $dbConn = new PDO('oci:dbname=' . $tns, $user, $passwd);
} catch (PDOException $e){
    echo $e->getMessage();
}

$stmt = $dbConn->query('SELECT * FROM BACUST00');
$row = $stmt->fetch();

echo '';
var_dump($row);
echo '';
```

```
export ORACLE_HOME=/usr/lib/oracle/11.2/client64
export ORACLE_BASE=/usr/lib/oracle/11.2
export LD_LIBRARY_PATH=$ORACLE_HOME/lib:$LD_LIBRARY_PATH
export PATH=$ORACLE_HOME/bin:$PATH
```

# 大於等於小於 縮寫

- **lt：less than** 小於
- **le：less than or equal to** 小於等於
- **eq：equal to** 等於
- **ne：not equal to** 不等於
- **ge：greater than or equal to** 大於等於
- **gt：greater than** 大於

# 常用函數 empty isset isnull 比較

[![image-1637654833415.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-11/scaled-1680-/S9se8GNFeJJkm3lm-image-1637654833415.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-11/S9se8GNFeJJkm3lm-image-1637654833415.png)

# Centos7 安裝 php7.4

#### 新增Remi repository

```shell
yum install http://rpms.remirepo.net/enterprise/remi-release-7.rpm
yum install epel-release yum-utils
```

開啟PHP 7.4  
`yum-config-manager --enable remi-php74`

安裝PHP 7.4  
`yum install php`

驗證  
`php -v`

安裝PHP 7.4擴充功能

```shell
yum install php-devel php-xmlrpc php-bcmath php-recode php-pdo php-mysqlnd \
php-enchant php-common php-cli php-gd php-embedded php-ldap php-process \
php-pecl-zip php-pgsql php-dba php-json php-soap php-fpm php-pear php-odbc \
php-intl php-xml php-snmp php-mbstring php-imagick -y
```

如要降級安裝php 7.3，則先移除原先的安裝，再執行  
`yum-config-manager --disable remi-php74<br></br>yum-config-manager --enable remi-php73`

# PHP_判斷json物件

```PHP
function isJson($string) {
   json_decode($string);
   return json_last_error() === JSON_ERROR_NONE;
}
```

# PHP_使用curl 上傳資料

client shell

```shell
# curl 上傳資料
# {file} : 可更改,$_file 的key就會不同
# curl -X POST -F "{file}=@{檔案路徑}" {api url}
curl -X POST -F "file=@/tmp/sms_count.csv" http://192.168.1.1/api/v1/SMS/UploadSmsCount

```

server 端 php

```PHP

// 取得$_file 資料
// $file 
Array
(
    [file] => Array
        (
            [name] => sms_count.csv
            [type] => application/octet-stream
            [tmp_name] => /tmp/phpAvxPxQ
            [error] => 0
            [size] => 1175
        )

)
  
// 取得內容 => $data
$data = file_get_contents($_FILES['file']['tmp_name']);  

// 如果csv 是 big5  記得轉碼
$data = iconv("BIG5","UTF-8",$data);

```

# PHP_連線postgresql

安裝postgrel extenstion

```shell
yum install php-pgsql -y
```

修改 /var/lib/pgsql/12/data/pg\_hba.conf

```
host    all             all             10.60.87.0/24           md5
```

重啟服務

```
service postgresql-12 restart
```

# PHP_實現異步請求

[PHP教程：php中使用fsockopen實現異步請求（代碼示例） (insci.cn)](https://tw.insci.cn/job_87377.html)

[《面試官別再問》PHP運用多執行緒(Multi-thread)實現非阻塞方法 | 阿宅工作日誌 (bps1025.blogspot.com)](https://bps1025.blogspot.com/2018/10/php-multi-threadprocopen.html)

# PHP_檔案相關

- #### 判斷資料夾是否存在，建立資料夾

```PHP
if ( !file_exists( $dir ) || !is_dir( $dir) ) {   mkdir($dir); }
```

- #### 遞回查詢資料夾內檔案

```PHP

function listdirs($dirf, $mark=1) {

    // LOB_MARK	若檔案為資料夾，在回傳檔案路徑的最後面加上斜線"\"
    // GLOB_NOSORT	保持檔案路徑在原資料夾的出現順序(不重新排序)
    // GLOB_NOCHECK	若找不到匹配的檔案路徑，回傳匹配的條件字串
    // GLOB_NOESCAPE	不要將反斜線視為跳脫字元
    // GLOB_BRACE	將 {a,b,c} 視為搜尋 'a', 'b', 或 'c'
    // GLOB_ONLYDIR	只列出資料夾路徑
    // GLOB_ERR	發生讀取錯誤時停止動作(像是無法讀取的資料夾)，預設是「忽略錯誤」
    $alldirs = array();
    if ($mark==1)
    {
        $alldirs = null;
    }
    $dirs = glob($dirf . '/*', GLOB_NOSORT);
    if (count($dirs) > 0) {
        foreach ($dirs as $d) $alldirs[] = $d;
    }
    foreach ($dirs as $dirf) listdirs($dirf, 0);
    return $alldirs;
}
$dirf="/tmp";
$result_data = listdirs($dirf, $mark=1);
echo print_r($result_data,true);
```

執行系統命令

```PHP
  function osCommand( $CMD="" )
  {
    $ret="";
    
    if( $CMD=="" )
      return $ret;
      
    if( ($fp = popen($CMD,"r")) )
    {
      $ret="";
      while( !feof( $fp ) )
        $ret .= fgets( $fp )."\n";
      fclose( $fp );
    }
    
    return $ret;
  }
if(!is_dir($log_folder)){
	mkdir($log_folder);
	osCommand( "chown webmaster.webmaster $log_folder");
}
```

# prettyPrint

```PHP
function prettyPrint( $json )
{
    $result = '';
    $level = 0;
    $in_quotes = false;
    $in_escape = false;
    $ends_line_level = NULL;
    $json_length = strlen( $json );

    for( $i = 0; $i < $json_length; $i++ ) {
        $char = $json[$i];
        $new_line_level = NULL;
        $post = "";
        if( $ends_line_level !== NULL ) {
            $new_line_level = $ends_line_level;
            $ends_line_level = NULL;
        }
        if ( $in_escape ) {
            $in_escape = false;
        } else if( $char === '"' ) {
            $in_quotes = !$in_quotes;
        } else if( ! $in_quotes ) {
            switch( $char ) {
                case '}': case ']':
                    $level--;
                    $ends_line_level = NULL;
                    $new_line_level = $level;
                    break;

                case '{': case '[':
                    $level++;
                case ',':
                    $ends_line_level = $level;
                    break;

                case ':':
                    $post = " ";
                    break;

                case " ": case "\t": case "\n": case "\r":
                    $char = "";
                    $ends_line_level = $new_line_level;
                    $new_line_level = NULL;
                    break;
            }
        } else if ( $char === '\\' ) {
            $in_escape = true;
        }
        if( $new_line_level !== NULL ) {
            $result .= "\n".str_repeat( "\t", $new_line_level );
        }
        $result .= $char.$post;
    }

    return $result;
}
```

# require和include的不同

<div id="bkmrk-include%28%27func.php%27%29%3B"><div>```
<span class="hljs-keyword">include</span>(<span class="hljs-string">'func.php'</span>);
<span class="hljs-keyword">include</span> <span class="hljs-string">'func.php'</span>;

<span class="hljs-keyword">require</span>(<span class="hljs-string">'lib.php'</span>);
<span class="hljs-keyword">require</span>(<span class="hljs-string">'lib.php'</span>);

<span class="hljs-keyword">include_once</span> <span class="hljs-string">'mydb.php'</span>;
<span class="hljs-keyword">include_once</span>(<span class="hljs-string">'mydb.php'</span>);

<span class="hljs-keyword">require_once</span> <span class="hljs-string">'copyright.php'</span>;
<span class="hljs-keyword">require_once</span>(<span class="hljs-string">'copyright.php'</span>);
```

</div></div><div id="bkmrk-%2Arequire%E5%92%8Cinclude%E7%9A%84%E4%B8%8D%E5%90%8C">*require和include的不同</div><div id="bkmrk-require"><div>- **require**

</div></div>> - 適合用來引入`靜態`的內容
> - 如果include進來的檔案發生錯誤的話，會顯示`警告`，`不會立刻停止`

<div id="bkmrk-include"><div>- **include**

</div></div>> - 則適合用來引入`動態`的程式碼。
> - 而require 則是會顯示`錯誤`，`立刻終止程式`，不再往下執行。

<div id="bkmrk-require_once%E5%92%8Cinclude"><div>---

- **require\_once**和**include\_once**

</div></div>> 使用方法跟require、include一樣，差別在於在引入檔案前
> 
> 會先**檢查檔案是否已經在其他地方被引入過了**，若有，就不會再重複引入。

# mac 安裝 php 7.4

mac 安裝 php 7.4

```
brew tap shivammathur/php
brew install php@7.4
brew link --overwrite --force php@7.4
php -v
```

# Python

# 【Python】安裝psycopg2錯誤

```shell
[root@proxy ~]# yum -y install  gcc gcc-c++ make 
[root@proxy ~]# pip3 install psycopg2
Error: pg_config executable not found.

#找到pg_config 真實路徑(有可能自訂安裝路徑不同)
[root@proxy ~]export PATH=/usr/pgsql-12/bin/:$PATH

[root@proxy ~]# yum install -y postgresql-devel
./psycopg/psycopg.h:35:20: fatal error: Python.h: No such file or directory
 #include <Python.h>
                    ^
compilation terminated.

[root@proxy ~]# yum install -y python36-devel

[root@proxy ~]# pip3 install psycopg2
```

# 【Python】刪除 window 下執行時產生的暫存檔

```
for /f "delims=" %F in ('Dir /B /S .\*.py ^|findstr /IE "\\migrations\\[^\\]*.py"^|findstr /IEV "\\__init__.py" ') Do @echo del "%F"
```

# 【Python】登入網路設備擷取資訊

- [https://www.cnblogs.com/guxh/p/12375801.html](https://www.cnblogs.com/guxh/p/12375801.html)
- [https://www.cnblogs.com/guxh/p/9831226.html](https://www.cnblogs.com/guxh/p/9831226.html)

netmiko

- [https://www.csdn.net/tags/MtTaEgxsNDk2NDIzLWJsb2cO0O0O.html](https://www.csdn.net/tags/MtTaEgxsNDk2NDIzLWJsb2cO0O0O.html)
- [https://zhuanlan.zhihu.com/p/367962211](https://zhuanlan.zhihu.com/p/367962211)
- [最强Netmiko攻略疑难杂症篇 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/546340640)

# 【Python】程式練習-ZeroJudge網站

## ZeroJudge網站

[http://zerojudge.tw/](http://zerojudge.tw/Submissions)

### 適合所有中學生及初學者的 Online Judge 系統

AC (Accept): 即表示通過  
NA (Not Accept): 在多測資點的題目中若未通過所有測資點則出現 NA  
WA (Wrong Answer): 表示答案錯誤，並在訊息中指出錯誤行數及正確答案  
TLE (Time Limit Exceed): 表示執行超過時間限制  
MLE (Memory Limit Exceed): 表示程序執行超過記憶體限制  
OLE (Output Limit Exceed): 表示程序輸出檔超過限制  
RE (Runtime Error): 表示執行時錯誤，通常為記憶體配置錯誤 如：使用了超過陣列大小的位置  
RF (Restricted Function): 表示使用了被禁止使用的函式，並在錯誤訊息中指明使用了什麼不合法的函式。  
CE (Compile Error): 表示編譯錯誤，並在訊息中列出完整錯誤訊息，以利判斷。 [關於編譯器](https://zerojudge.tw/UserGuide.html#compiler)  
SE (System Error): 包含 Compile, Runtime 等未定義錯誤均屬於 System Error

# 【Python】解析Python模組(Module)和套件(Package)的概念(轉)

當我們在開發大型應用程式時，如果沒有適當的組織程式碼，除了會降低開發的效率外，也不易於維護，所以模組(Module)化就顯得相當的重要，讓程式碼能夠透過引用的方式來重複使用，提升重用性(Reusable)。

  
但是隨著專案模組(Module)的增加，將難以管理及問題的追蹤，這時候就能將模組(Module)打包成套件(Package)，利用其階層式的結構來彈性規劃模組(Module)。

  
本篇文章就帶大家瞭解Python模組(Module)及套件(Package)的重要觀念，包含：

**什麼是模組(Module)**  
**模組引用方式(Import)**  
**什麼是套件(Package)**  
**dir()函式(dir function)**

將模組當作腳本來執行(Executing a Module as a Script)  
一、什麼是模組(Module)  
模組(Module)就是一個檔案，包含了相關性較高的程式碼。隨著應用程式的開發規模越來越大，我們不可能把所有的程式碼都寫在同一份Python檔案中，一定會將關聯性較高的程式碼抽出來放在不同的檔案中來形成模組(Module)，主程式再透過引用的方式來使用。所以模組(Module)可以提高程式碼的重用性(Reusable)且易於維護。

  
假設我們現在要開發一個部落格，主程式為 app.py ，在還沒有模組化時，程式碼可能長得像這樣：

```Python
#取得作者
def get_author():
    return "Mike"
#取得電子郵件
def get_email():
    return "learncodewithmike@gmail.com"
#新增文章
def add_post(title):
    pass
#刪除文章
def delete_post(title):
    pass
add_post()
author = get_author()
email = get_email()
```

以此範例來說，取得作者及電子郵件可以獨立出來建立一個關於模組(about.py)，而新增及刪除文章則可以獨立出來為文章模組(post.py)，專門處理文章相關的動作，如下範例：  
**about.py**

```Python
#取得作者
def get_author():
return "Mike"
#取得電子郵件
def get_email():
return "learncodewithmike@gmail.com"
post.py

#新增文章
def add_post(title):
pass
#刪除文章
def delete_post(title):
pass
```

**post.py**

```Python
#新增文章
def add_post(title):
    pass
#刪除文章
def delete_post(title):
    pass
```

當然，模組(Module)除了可以包含函式(Function)外，也可以為類別(Class)，我們以 post.py 為例：

```Python
class Post:
    # 建構式
    def __init__(self):
        self.titles = []
    # 新增文章
    def add_post(self, title):
        self.titles.append(title)
    # 刪除文章
    def delete_post(self, title):
        self.titles.remove(title)
```

所以現在我們專案中有一個主程式 app.py 及兩個模組(Module)，分別為 about.py 和 post.py。

二、模組引用方式(Import)  
我們將程式碼進行模組化後，主程式 app.py 要如何使用呢?首先，可以使用 from-import 語法，如下範例：

```Python
# 引用模組中的特定物件
from post import Post
from about import get_author, get_email
p = Post()
p.add_post("Python Programming")
author = get_author()
email = get_email()
print(p.titles)  #執行結果：['Python Programming']
print(author)  #執行結果：Mike
print(email)  #執行結果：learncodewithmike@gmail.com
```

from 之後加上模組(Module)的檔名，注意沒有 .py 副檔名，接著 import 引用所需的物件。

當然，在 import 之後可以使用 \* 來引用模組中的所有物件，但是這樣的寫法，可能會在引用的過程中，發生同名方法覆寫(Method Overriding)的風險，所以建議引用所需要的物件即可。另一種語法則是透過 import 語法，如下範例：

```Python
# 引用整個模組
import post
import about
p = post.Post()
p.add_post("Python Programming")
author = about.get_author()
email = about.get_email()
print(p.titles) #執行結果：['Python Programming']
print(author) #執行結果：Mike
print(email) #執行結果：learncodewithmike@gmail.com
```

import 之後加上模組(Module)的檔名，和上一個語法不一樣的地方是，此語法雖然引用整個模組(Module)，但是在主程式中必須透過模組(Module)的名稱來存取其中的成員。

  
在主程式 app.py 中引用模組(Module)，並且執行後，會發現多了一個 pycache 資料夾，如下圖：

[![image-1657187117735.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/scaled-1680-/kkxyUWvHQh0TeEaa-image-1657187117735.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/kkxyUWvHQh0TeEaa-image-1657187117735.png)

<div id="bkmrk-%E9%80%99%E5%80%8B%E8%B3%87%E6%96%99%E5%A4%BE%E4%B8%AD%EF%BC%8C%E5%8F%AF%E4%BB%A5%E7%9C%8B%E5%88%B0%E5%8C%85%E5%90%AB%E4%BA%86%E5%BC%95%E7%94%A8%E6%A8%A1%E7%B5%84%E7%9A%84%E5%B7%B2">這個資料夾中，可以看到包含了引用模組的已編譯檔案，當下一次執行主程式 app.py 時，Python編譯器看到已編譯的模組檔案，會直接載入該模組(Module)，而省略編譯的動作，藉此來加速載入模組(Module)的速度。</div>  
當然Python編譯器在每一次執行時，會檢查來源模組及已編譯檔案的時間，當來源模組的時間較新，則代表該模組(Module)有經過修改，則Python編譯器會再編譯一次，更新已編譯檔案。

  
各位有沒有覺得奇怪，那為什麼沒有 app.py 的已編譯檔案，因為在此範例中，我們將 app.py 當作程式的進入點，所以每一次執行python app.py 指令時，Python編譯器都要進行編譯，所以沒有將 app.py 進行快取的動作。  
三、什麼是套件(Package)  
就是一個容器(資料夾)，包含了一個或多個的模組(Module)，並且擁有\_\_init\_\_.py檔案，其中可以撰寫套件(Package)初始化的程式碼。

  
我們將程式碼模組化後，專案中的模組(Module)就會越來越多，這時候就可以再將相似的模組(Module)組織為套件(Package)。那要如何建立套件(Package)呢?

  
舉例來說，現在我們要將專案中的 post.py 及 about.py 模組(Module)打包為部落格套件(Package)，首先，建立blog資料夾，接著在資料夾中新增\_\_init\_\_.py檔案，最後將 post.py 及 about.py 模組(Module)移至blog資料夾中，如下範例：

[![image-1657187804134.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/scaled-1680-/nHF23hp4pAZdMf03-image-1657187804134.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/nHF23hp4pAZdMf03-image-1657187804134.png)

<div id="bkmrk-%E8%80%8C%E5%9C%A8%E4%B8%BB%E7%A8%8B%E5%BC%8F-app.py-%E4%B8%AD%E5%BC%95%E7%94%A8%E5%A5%97%E4%BB%B6%28p">而在主程式 app.py 中引用套件(Package)的方式和模組(Module)大同小異，我們先來看 from-import語法，如下範例：</div>```Python
# 從套件中引用模組
from blog import post
from blog import about
p = post.Post()
p.add_post("Python Programming")
author = about.get_author()
email = about.get_email()
```

另一個引用套件(Package)的 import 語法如下範例：

```Python
# 從套件中引用模組
import blog.post
import blog.about
p = blog.post.Post()
p.add_post("Python Programming")
author = blog.about.get_author()
email = blog.about.get_email()

```

四、dir()函式(dir function)  
Python提供了一個內建函式dir()，用來顯示物件(Object)的屬性(Attribute)及方法(Method)，我們利用此函式(Function)來看一下模組(Module)所擁有的屬性(Attribute)及方法(Method)，如下範例：

```Python
#從blog套件引用about模組
from blog import about
print(dir(about))
```

[![image-1612233962302.png](http://bookstack/uploads/images/gallery/2021-02/scaled-1680-/image-1612233962302.png)](http://bookstack/uploads/images/gallery/2021-02/image-1612233962302.png)

從執行結果可以看到模組(Module)中有自建的get\_author及get\_email方法(Method)，其餘的則是Python自動幫我們產生的，我們來看幾個常用的屬性(Attribute)，如下範例：

```Python
# 從blog套件引用about模組
from blog import about
print(about.__name__) # 模組名稱
print(about.__package__) # 套件名稱
print(about.__file__) # 模組的檔名及路徑
```

[![image-1612233998801.png](http://bookstack/uploads/images/gallery/2021-02/scaled-1680-/image-1612233998801.png)](http://bookstack/uploads/images/gallery/2021-02/image-1612233998801.png)

五、將模組當作腳本來執行(Executing a Module as a Script)

我們來看一個範例，在about模組(Module)中加上以下程式碼，並且執行該模組(Module)，如下範例：

```Python
#取得作者
def get_author():
return "Mike"
#取得電子郵件
def get_email():
return "learncodewithmike@gmail.com"
print("about module name: ", __name__)
# 執行結果：about module name: __main__
```

  
而這時候換成執行 app.py ，\_\_name\_\_屬性(Attribute)則為blog.about，我們就可以利用這個特性，撰寫腳本來彈性的控制當執行模組(Module)的檔案時，要進行哪些行為，而這些行為是在被其他模組(Module)引用時，不會被執行的，如下範例：

```Python
#取得作者
def get_author():
return "Mike"
#取得電子郵件
def get_email():
return "learncodewithmike@gmail.com"
if __name__ == "__main__":
print("about module initialized.")
get_author()
```

範例中將about模組(Module)加上了判斷式，當執行about模組(Module)時，\_\_name\_\_屬性(Attribute)為\_\_main\_\_，所以會執行我們設定的任務，而這些任務是在執行主程式 app.py 時，不會被執行的，因為\_\_name\_\_屬性(Attribute)為blog.about。

六、小結  
以上就是Python模組(Module)及套件(Package)的重要觀念，除了能夠提高程式碼的重用性(Reusable)外，也有利於未來的單元測試及維護。

# 【Python】psycopg2 防止SQL injection(轉)

[使用Python防止SQL注入攻击\_似繁星跌入梦的博客-CSDN博客\_python防止sql注入](https://blog.csdn.net/qiulin_wu/article/details/106216595)

[![image-1657780185170.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/scaled-1680-/ZKq4jk8z41FWKVvQ-image-1657780185170.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/ZKq4jk8z41FWKVvQ-image-1657780185170.png)

# 【Python】Pyenv 版本管理工具

[來源：\[Python 教學\] 如何切換 Python 版本，讓 Pyenv 幫你輕鬆管理版本 | Max行銷誌 (maxlist.xyz)](https://www.maxlist.xyz/2022/05/06/python-pyenv/)

 安裝

```shell
# 安裝相關套件
$ brew update
$ brew install openssl readline sqlite3 xz zlib
$ brew install pyenv
```

```shell
# 加入啟動環境變數
$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc
$ echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc
$ echo 'eval "$(pyenv init -)"' >> ~/.zshrc
```

```shell
# 進入 /python-demo 重新初始化環境
cd /python-demo
# 曾經設定過才要刪除
# rm -rf .venv 
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```

常用指令

```shell
# 如果安裝過pyenv 記得更新才能安裝較新的python版本
brew update
brew upgrade pyenv

# 查看当前版本
pyenv version

# 查看所有版本
pyenv versions

# 查看所有可安装的版本
pyenv install --list

# 安装指定版本
pyenv install 3.6.5
# 安装新版本后rehash一下
pyenv rehash

# 删除指定版本
pyenv uninstall 3.5.2

# 指定全局版本
pyenv global 3.6.5

# 指定多个全局版本, 3版本优先
pyenv global 3.6.5 2.7.14

# 实际上当你切换版本后, 相应的pip和包仓库都是会自动切换过去的

# 切回系統預設版本
pyenv global system

```

---

### 使用`pyenv-virtualenv`建立虛擬環境

如果你希望使用虛擬環境，可以安裝並使用`pyenv-virtualenv`：

1. **安裝`pyenv-virtualenv`**：
    
    ```
    <span class="hljs-keyword">brew </span><span class="hljs-keyword">install </span>pyenv-virtualenv
    
    ```
    
    將以下內容新增至您的`~/.bashrc`或`~/.zshrc`文件：
    
    ```
    <span class="hljs-built_in">eval</span> <span class="hljs-string">"<span class="hljs-variable">$(pyenv virtualenv-init -)</span>"</span>
    
    ```
    
    然後重新載入shell設定檔：
    
    ```
    <span class="hljs-built_in">source</span> ~/.bashrc  <span class="hljs-comment"># 如果使用 bash</span>
    <span class="hljs-built_in">source</span> ~/.zshrc   <span class="hljs-comment"># 如果使用 zsh</span>
    
    ```
2. **創建Python 2虛擬環境**：
    
    ```
    <span class="hljs-selector-tag">pyenv</span> <span class="hljs-selector-tag">virtualenv</span> 2<span class="hljs-selector-class">.7</span><span class="hljs-selector-class">.18</span> <span class="hljs-selector-tag">my-virtual-env</span>
    
    ```
3. **啟動虛擬環境**：
    
    ```
    pyenv <span class="hljs-built_in">activate</span> <span class="hljs-keyword">my</span>-virtual-env
    
    ```
4. **失效虛擬環境**：
    
    ```
    <span class="hljs-attribute">pyenv deactivate</span>
    
    ```

透過上述步驟，您可以在 macOS 上使用`pyenv`安裝和管理 Python 2 版本，並在不同的專案之間輕鬆切換。

# 【Python】python讀取json

在 Python 中，JSON 是一種常用的資料格式，用來儲存和交換資料。Python 提供內建的 `json` 模組來讀取與處理 JSON 資料，支援從檔案或字串中解析 JSON，以及將資料轉換成 JSON 格式輸出。

以下是 Python 中讀取 JSON 的相關用法：

---

## **1. Python 的 `json` 模組介紹**

Python 的 `json` 模組提供以下幾個主要功能：

- `json.load()`：從檔案中讀取 JSON 資料。
- `json.loads()`：從字串中解析 JSON 資料。
- `json.dump()`：將 Python 資料寫入檔案，並格式化為 JSON。
- `json.dumps()`：將 Python 資料轉換為 JSON 格式的字串。

---

## **2. 讀取 JSON 的方式**

### **2.1 從檔案讀取 JSON**

假設有一個 `data.json` 檔案，內容如下：

```json
{
    "name": "Alice",
    "age": 25,
    "skills": ["Python", "JavaScript"]
}

```

用法：

```python
import json

# 打開 JSON 檔案並讀取
with open('data.json', 'r', encoding='utf-8') as file:
    data = json.load(file)

# 輸出讀取的資料
print(data)           # {'name': 'Alice', 'age': 25, 'skills': ['Python', 'JavaScript']}
print(data['name'])   # Alice

```

---

### **2.2 從字串解析 JSON**

如果 JSON 資料是以字串的形式存在，可以使用 `json.loads()` 來解析：

```python
import json

# JSON 字串
json_string = '{"name": "Bob", "age": 30, "skills": ["Java", "C++"]}'

# 解析 JSON 字串
data = json.loads(json_string)

# 輸出讀取的資料
print(data)           # {'name': 'Bob', 'age': 30, 'skills': ['Java', 'C++']}
print(data['skills']) # ['Java', 'C++']

```

---

## **3. JSON 資料寫入檔案**

### **3.1 寫入 JSON 到檔案**

使用 `json.dump()` 將 Python 資料寫入 JSON 檔案：

```python
import json

# Python 資料
data = {
    "name": "Charlie",
    "age": 35,
    "skills": ["Go", "Rust"]
}

# 寫入 JSON 檔案
with open('output.json', 'w', encoding='utf-8') as file:
    json.dump(data, file, ensure_ascii=False, indent=4)

print("JSON 資料已成功寫入檔案")

```

- **`ensure_ascii=False`**：避免非 ASCII 字元（例如中文）被編碼為 Unicode（如 `\u4e2d\u6587`）。
- **`indent=4`**：設定縮排，讓輸出的 JSON 更易於閱讀。

輸出的檔案內容：

```json
{
    "name": "Charlie",
    "age": 35,
    "skills": [
        "Go",
        "Rust"
    ]
}

```

---

### **3.2 將 Python 資料轉換為 JSON 字串**

使用 `json.dumps()` 將資料轉換為 JSON 格式字串：

```python
import json

# Python 資料
data = {
    "name": "Daisy",
    "age": 28,
    "skills": ["HTML", "CSS"]
}

# 轉換為 JSON 字串
json_string = json.dumps(data, ensure_ascii=False, indent=2)
print(json_string)

```

輸出：

```json
{
  "name": "Daisy",
  "age": 28,
  "skills": [
    "HTML",
    "CSS"
  ]
}

```

---

## **4. 常見的 JSON 資料類型對應**

Python 與 JSON 的資料類型有一對一的對應關係：

<table id="bkmrk-json-%E9%A1%9E%E5%9E%8B-python-%E9%A1%9E%E5%9E%8B-ob"><thead><tr><th>JSON 類型</th><th>Python 類型</th></tr></thead><tbody><tr><td>Object</td><td>Dictionary (`dict`)</td></tr><tr><td>Array</td><td>List (`list`)</td></tr><tr><td>String</td><td>String (`str`)</td></tr><tr><td>Number (int/float)</td><td>Integer (`int`)/Float (`float`)</td></tr><tr><td>Boolean</td><td>Boolean (`bool`)</td></tr><tr><td>null</td><td>None</td></tr></tbody></table>

範例：

```python
json_string = '''
{
    "name": "Eve",
    "age": 40,
    "is_active": true,
    "children": null
}
'''

data = json.loads(json_string)
print(type(data))           # <class 'dict'>
print(data['is_active'])    # True (Python 的布林值)
print(data['children'])     # None (Python 的 None)

```

---

## **5. 捕捉 JSON 解析錯誤**

處理 JSON 時，可能會遇到格式不正確的情況，可以透過 `try-except` 捕捉例外：

```python
import json

json_string = '{"name": "Frank", "age": 45,'  # 少了結尾的 }

try:
    data = json.loads(json_string)
except json.JSONDecodeError as e:
    print(f"JSON 解析錯誤: {e}")

```

輸出：

```
JSON 解析錯誤: Expecting property name enclosed in double quotes: line 1 column 30 (char 29)

```

---

## **6. 自訂 JSON 編碼與解碼**

### **6.1 處理非內建類型**

如果你需要將自定義類型的資料轉換為 JSON，可以透過繼承 `json.JSONEncoder` 來實現：

範例：

```python
import json

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

class PersonEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Person):
            return {"name": obj.name, "age": obj.age}
        return super().default(obj)

person = Person("Grace", 50)
json_string = json.dumps(person, cls=PersonEncoder, indent=4)
print(json_string)

```

輸出：

```json
{
    "name": "Grace",
    "age": 50
}

```

### **6.2 自訂解碼器**

你也可以自訂解碼器將 JSON 資料轉換為自定義的 Python 類別：

```python
import json

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

def decode_person(dct):
    if "name" in dct and "age" in dct:
        return Person(dct['name'], dct['age'])
    return dct

json_string = '{"name": "Henry", "age": 60}'
person = json.loads(json_string, object_hook=decode_person)
print(type(person))  # <class '__main__.Person'>
print(person.name)   # Henry

```

---

## **結論**

- 使用 `json.load()` 和 `json.loads()` 來解析 JSON 檔案或字串。
- 使用 `json.dump()` 和 `json.dumps()` 將 Python 資料輸出為 JSON 格式。
- 可以透過 `ensure_ascii` 和 `indent` 參數來控制 JSON 的輸出格式。
- 若需處理自定義類型，可使用自訂的編碼器和解碼器。
- 遇到格式錯誤時，使用 `try-except` 捕捉 `json.JSONDecodeError`。

這些功能幾乎涵蓋了日常開發中處理 JSON 資料的需求，讓你能更高效地處理結構化數據！

---

json to distionary

```Python
import json

# json 的資料形式字串
x =  '{ "name":"jim", "age":25, "city":"Taiwan"}'

# 轉換json
person = json.loads(x)

print(type(person)) #<class 'dict'>
print(person){'name': 'jim', 'age': 25, 'city': 'Taiwan'}
print(person['age']) #25
```

distionary to json

```Python
import json

person = {'name': 'jim', 'age': 25, 'city': 'Taiwan'}

data = json.dumps(person)

print(type(data)) #<class 'str'>
print(data) #{"name": "jim", "age": 25, "city": "Taiwan"}
```

# 【Python】selenium自動播放flash

[python selenium firefox 控制devtools 一些线索\_wujiuqier的博客-CSDN博客](https://blog.csdn.net/qq_35977139/article/details/114835635)

  
軟體載點

- [https://ftp.mozilla.org/pub/mozilla.org/firefox/releases/](https://ftp.mozilla.org/pub/mozilla.org/firefox/releases/ "舊版本下載")
- [https://github.com/mozilla/geckodriver/releases](https://github.com/mozilla/geckodriver/releases)

```
後來測試可用的組合為

python 3.7.0
firefox 57.0.4
geckodriver 0.19.1
selenium 3.141.0

```

版本55或以上的Firefox不支持Flash自动播放。  
建议使用Firefox 52.9.0 延长支持版。  
此版本需要使用Gecko Driver 0.18.0才能正常使用。但是不支持 set\_window\_size（会报错selenium.common.exceptions.WebDriverException: Message: setWindowRect），所以必须手动调整视口尺寸。 应该使用find\_element\_by\_id(element\_id).screenshot(output\_file\_name)的方法来截长图，或者也可以用find\_elements\_by\_tag\_name('body')\[0\].screenshot(output\_file\_name)的方法来截整页长图。  
自动播放Flash的设置（参考https://blog.csdn.net/STL\_CC/article/details/104968669）：

  
此版本还没有移除GCLI开发者工具栏，可以通过快捷键 `Shift + F2`呼出。  
调整视口尺寸的GCLI命令是`resize to 4320 7680`。第一个参数是宽，第二个参数是高。  
截图的命令是

既然不能控制视口，那就直接控制外层`div`的样式，然后执行元素节点截图。直接用`execute_script`即可。  
综上，截取小花仙人物面板形象的操作有：

[![image-1665654519510.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-10/scaled-1680-/ldSkMNdlKCrXoVbf-image-1665654519510.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-10/ldSkMNdlKCrXoVbf-image-1665654519510.png)

```Python

from time import sleep
from selenium import webdriver
import win32api
import win32gui
import win32con
import os
import requests
import sys
import time
from PIL import ImageGrab
import win32com.client

login_info = [
    {
        "file_name": "db1.jpeg",
        "url": "https://1.2.3.4/em/console/database/instance/waitDetails?event=doLoad&target=mall&type=oracle_database&waitClass=Overview&datasource=SQL",
        "username": "aa",
        "title_id": None,
        "password": "aaaaaaaa",
        "hwnd": None,
        "driver": None},
    {
        "file_name": "db2.jpeg",
        "url": "https://1.2.3.4/em/faces/sdk/nonFacesWrapper?target=reportdb&_em.coBM=%2Fconsole%2Fdatabase%2Finstance%2FwaitDetails%3Fevent%26target%3Dreportdb%26type%3Doracle_database%26waitClass%3DOverview%26tabName%3Doverview%26selectedBand%3D1%26leftEdge%3Dundefined&type=oracle_database",
        "username": "bb",
        "password": "bbbbbbbb",
        "title_id": None,
        "j_username": "cc",
        "j_password": "cccccccc",
        "hwnd": None,
        "driver": None}
]


def api_upload(filename):
    url = "http://192.168.1.1/api/v1/db/uploadIMG.php"
    local_folder_path = getRootPath()  # 取得目前執行檔目錄
    image = local_folder_path + "\\" + filename
    files = {"file": (filename, open(image, "rb"), "rb")}
    response = requests.request("POST", url, data=None, files=files)
    # print(response.text)


def getRootPath():
    return os.path.dirname(os.path.abspath(__file__))


def main():
    for config in login_info:
        try:
            profile = webdriver.FirefoxProfile()
            profile.set_preference('plugin.state.flash', 2)
            profile.set_preference('security.tls.version.min', 1)
            profile.set_preference('security.insecure_field_warning.contextual.enabled', False)
            if config["driver"] == None:
                driver = webdriver.Firefox(executable_path=r'C:\screenshot\geckodriver.exe', firefox_profile=profile)
                config["driver"] = driver
                driver.get(config["url"])

                if config["file_name"] == 'mallDB.jpeg':
                    username_input = driver.find_element_by_id("M__Id")
                    username_input.send_keys(config["username"])
                    password_input = driver.find_element_by_id("M__Ida")
                    password_input.send_keys(config["password"])
                    driver.find_element_by_xpath('//table/tbody/tr[4]/td[3]/a/img').click()
                    sleep(7)
                    config["title_id"] = win32gui.GetForegroundWindow()
                elif config["file_name"] == 'reportDB.jpeg' or config["file_name"] == 'wmsDB.jpeg' or config["file_name"] == 'scmDB.jpeg':
                    j_username_input = driver.find_element_by_id("j_username::content")
                    j_username_input.send_keys(config["j_username"])
                    j_password_input = driver.find_element_by_id("j_password::content")
                    j_password_input.send_keys(config["j_password"])
                    login_btn = driver.find_element_by_id("login")
                    login_btn.click()
                    sleep(5)
                    username_input2 = driver.find_element_by_id("emT:r1:0:r1:0:username::content")
                    username_input2.send_keys(config["username"])
                    password_input2 = driver.find_element_by_id("emT:r1:0:r1:0:password::content")
                    password_input2.send_keys(config["password"])
                    login_btn_emT = driver.find_element_by_id("emT:logon")
                    login_btn_emT.click()
                    sleep(5)
                    config["title_id"] = win32gui.GetForegroundWindow()

                title_id = win32gui.GetWindowText(config["title_id"])
                hwnd = win32gui.FindWindow(None, title_id)
                shell = win32com.client.Dispatch("WScript.Shell")
                shell.SendKeys('%')
                win32gui.ShowWindow(hwnd, win32con.SW_MAXIMIZE)
            config["driver"].save_screenshot(config["file_name"])
            api_upload((config["file_name"]))
            sleep(3)

        except Exception as e:
            print("exception:", e)


if __name__ == "__main__":
    while True:
        main()

```

# 【Python】selenium性能優化

selenium性能優化

```Python
chrome_options = Options()
chrome_options.add_argument("--window-size=1920,1080")
#禁用插件
chrome_options.add_argument("--disable-extensions")

#無圖形化
chrome_options.add_argument("--headless")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--disable-software-rasterizer")

#禁用圖片
chrome_options.add_argument("blink-settings=imagesEnabled=false")

chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--ignore-certificate-errors')
#允許不安全內容(ssl問題)
chrome_options.add_argument('--allow-running-insecure-content')
driver = webdriver.Chrome(options=chrome_options)
```

# 【Python】【PrettyTable】 資料格式化排版工具

安裝PrettyTable

```
pip install PrettyTable
```

範例程式

```Python
from prettytable import PrettyTable
  x = PrettyTable()
  
  x.field_names = ["First name", "Last name", "Salary", "City", "DOB"]
  x.add_row(["Shubham", "Chauhan", 60000, "Lucknow", "22 Feb 1999"])
  x.add_row(["Saksham", "Chauhan", 50000, "Hardoi", "21 Aug 2000"])
  x.add_row(["Preeti", "Singh", 40000, "Unnao", "10 Jan 1995"])
  x.add_row(["Ayushi", "Chauhan", 65000, "Haridwar", "30 Jan 2002"])
  x.add_row(["Abhishek", "Rai", 70000, "Greater Noida", "16 Jan 1999"])
  x.add_row(["Dinesh", "Pratap", 80000, "Delhi", "3 Aug 1998"])
  x.add_row(["Chandra", "Kant", 85000, "Ghaziabad", "18 Sept 1997"])
  table = x.get_string()
  print(table)
```

```
+------------+-----------+--------+---------------+--------------+
| First name | Last name | Salary |      City     |     DOB      |
+------------+-----------+--------+---------------+--------------+
|  Shubham   |  Chauhan  | 60000  |    Lucknow    | 22 Feb 1999  |
|  Saksham   |  Chauhan  | 50000  |     Hardoi    | 21 Aug 2000  |
|   Preeti   |   Singh   | 40000  |     Unnao     | 10 Jan 1995  |
|   Ayushi   |  Chauhan  | 65000  |    Haridwar   | 30 Jan 2002  |
|  Abhishek  |    Rai    | 70000  | Greater Noida | 16 Jan 1999  |
|   Dinesh   |   Pratap  | 80000  |     Delhi     |  3 Aug 1998  |
|  Chandra   |    Kant   | 85000  |   Ghaziabad   | 18 Sept 1997 |
+------------+-----------+--------+---------------+--------------+
```

# 【Python】【tabulate】 資料格式化排版工具

安裝

```
pip3 install tabulate
```

範例

```Python
from tabulate import tabulate

data = [
    ["Alice", 25, "Engineer"],
    ["Bob", 30, "Developer"],
    ["Charlie", 35, "Manager"],
]

headers = ["Name", "Age", "Job"]

table = tabulate(data, headers=headers, tablefmt="psql")
print(table)

```

```
+---------+-----+------------+
| Name    | Age | Job        |
|---------+-----+------------|
| Alice   | 25  | Engineer   |
| Bob     | 30  | Developer  |
| Charlie | 35  | Manager    |
+---------+-----+------------+
```

你可以根據需要選擇不同的表格格式，例如 `"plain"`、`"simple"`、`"grid"` 等。`tabulate` 模組還支援對齊選項、數值格式化等功能，

`tabulate` 模組的 `tablefmt` 參數用於指定要使用的表格格式。下面列出了一些常用的 `tablefmt` 參數值：

1. `"plain"`: 這是默認的表格格式，使用簡單的 ASCII 字符。
2. `"simple"`: 使用更精簡的 ASCII 字符繪製表格。
3. `"grid"`: 使用 ASCII 字符繪製網格狀的表格。
4. `"fancy_grid"`: 使用更複雜的 ASCII 字符繪製網格狀的表格。
5. `"pipe"`: 使用竖线字符 `|` 繪製表格。
6. `"orgtbl"`: 使用 `+` 和 `-` 字符繪製表格。
7. `"jira"`: 生成符合 Jira Wiki 格式的表格。
8. `"presto"`: 生成符合 Presto 命令行客戶端輸出的表格格式。
9. `"pretty"`: 使用 Unicode 字符繪製表格，具有較好的可讀性。

這只是 `tablefmt` 參數的一些常見值，`tabulate` 還支援其他一些特殊格式，如 HTML、LaTeX 等。你可以參考 `tabulate` 模組的官方文件以獲取完整的 `tablefmt` 參數列表和相應的表格格式示例。

# 【Python】【環境建置】venv  虛擬環境建置

```
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -r requirements.txt
python3 todo.py
```

### `python3 -m venv .venv`

虛擬環境是一個獨立的 Python 環境，可以讓你在同一台機器上使用不同版本的 Python 和套件，而不會相互干擾。這對於開發和測試項目時非常有用，因為每個項目都可以有自己的獨立環境。

在命令 `python3 -m venv .venv` 中，`python3` 是用於執行 Python 3 解釋器的命令，`-m venv` 是告訴 Python 解釋器使用 venv 模組來建立虛擬環境，`.venv` 是虛擬環境的目錄名稱。

執行這個命令後，會在當前目錄下建立一個名為 `.venv` 的目錄，這個目錄就是虛擬環境的根目錄。在這個目錄中，會包含一個名為 `bin`（或 `Scripts` 在 Windows 上）的子目錄，其中包含了虛擬環境使用的 Python 解釋器和相關的工具。

要啟用虛擬環境，可以使用以下命令：

- 在 macOS/Linux 上：`source .venv/bin/activate`
- 在 Windows 上：`.venv\Scripts\activate.bat`

啟用虛擬環境後，你可以在該環境中安裝和使用 Python 套件，而這些套件不會影響到全域 Python 環境。當你在虛擬環境中完成工作後，可以使用 `deactivate` 命令來停用虛擬環境。

---

### 啟用 Python 虛擬環境 `ource .venv/bin/activate`

`source .venv/bin/activate` 是用於啟用 Python 虛擬環境的命令，該虛擬環境是使用 `python3 -m venv` 建立的。

當你執行這個命令時，它會讀取虛擬環境目錄（`.venv`）中的 `activate` 腳本並執行它。這個腳本會設置一些環境變數和修改你的 shell 提示符，以將你的 Python 環境切換到虛擬環境。

具體而言，`source .venv/bin/activate` 的作用如下：

1. 啟動虛擬環境：這個命令會將你的 shell 環境切換到虛擬環境中，使你在該環境中運行的 Python 解釋器和安裝的套件與全域環境隔離開來。
2. 設置環境變數：這個腳本會設置幾個環境變數，如 `PATH`、`PYTHONPATH` 等，以便在虛擬環境中優先使用該環境中的 Python 解釋器和套件。
3. 修改提示符：這個腳本還可以修改你的 shell 提示符，通常會在提示符前加上虛擬環境的名稱，以提醒你正在使用虛擬環境。

當虛擬環境被啟用後，你可以在該環境中使用 `pip` 命令安裝套件，執行 Python 腳本等。所有的操作都會在虛擬環境中進行，不會影響到全域環境。

如果你想停用虛擬環境，可以執行 `deactivate` 命令，它會恢復你的 shell 環境到原始狀態，不再使用虛擬環境。

# 【 Python】常用自訂函數

#### 特數字元轉譯

```Python
def escape_string(input_string):
    special_chars = ['<', '>', '&', '*', '_', '~', '`', '|', '#', '!']
    escaped_string = ""
    
    for char in input_string:
        if char in special_chars:
            escaped_string += '\\' + char
        else:
            escaped_string += char
    
    return escaped_string
```

# 【Python】import 用法

在 Python 中，`import` 是用來引入其他模組、套件或特定功能的關鍵字，讓你可以重複利用現有的程式碼，避免重複撰寫功能。以下是 `import` 的詳細說明及常見用法：

---

## **1. `import` 的基本概念**

`import` 用於引入一個 Python 模組或套件，讓你可以使用其中的函數、類別或變數。

### **模組與套件**

- **模組**：是一個 Python 檔案（以 `.py` 結尾），其中包含定義的函數、類別或變數。 
    - 範例：`math` 模組、你自己撰寫的 `mymodule.py`。
- **套件**：是一個包含多個模組的目錄，且該目錄下有 `__init__.py` 文件。 
    - 範例：`os` 套件、`requests` 套件。

---

## **2. `import` 的使用方式**

### **2.1 匯入整個模組**

- 語法：
    
    ```python
    import module_name
    
    ```
- 範例：
    
    ```python
    import math
    
    print(math.sqrt(16))  # 使用 math 模組中的 sqrt 函數
    
    ```
- 特點：需要使用 `模組名稱.功能` 的方式來存取模組內容。

---

### **2.2 匯入模組的特定部分**

- 語法：
    
    ```python
    from module_name import specific_function_or_variable
    
    ```
- 範例：
    
    ```python
    from math import sqrt
    
    print(sqrt(16))  # 直接使用 sqrt 函數，無需加上模組名稱
    
    ```
- 特點：只匯入需要的部分，節省記憶體，但可能引發命名衝突。

---

### **2.3 匯入模組並重新命名**

- 語法：
    
    ```python
    import module_name as alias
    
    ```
- 範例：
    
    ```python
    import numpy as np
    
    arr = np.array([1, 2, 3])
    print(arr)
    
    ```
- 特點：透過別名縮短模組名稱，讓程式碼更簡潔。

---

### **2.4 匯入模組所有內容**

- 語法：
    
    ```python
    from module_name import *
    
    ```
- 範例：
    
    ```python
    from math import *
    
    print(sqrt(16))  # 可以直接使用 math 中的所有功能
    
    ```
- 特點：
    
    
    - 匯入所有內容，但不推薦，因為可能導致命名衝突。
    - 建議明確列出需要匯入的內容（用 `__all__` 控制）。

---

### **2.5 匯入套件中的子模組**

- 語法：
    
    ```python
    from package_name import submodule_name
    
    ```
- 範例：
    
    ```python
    from os import path
    
    print(path.exists("example.txt"))  # 使用 os.path 模組中的 exists 函數
    
    ```

---

## **3. Python 搜尋模組的順序**

當執行 `import` 時，Python 按照以下順序尋找模組：

1. **內建模組**：Python 標準庫中的模組，例如 `math`、`os`。
2. **當前目錄**：程式執行時所在的目錄。
3. **PYTHONPATH**：環境變數中指定的路徑。
4. **全域安裝的目錄**：例如 `site-packages`。

如果找不到模組，會拋出 `ModuleNotFoundError`。

---

## **4. 常見的 import 模式比較**

<table id="bkmrk-%E5%8C%AF%E5%85%A5%E6%96%B9%E5%BC%8F-%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F-%E5%84%AA%E9%BB%9E-%E7%BC%BA%E9%BB%9E-impo"><thead><tr><th>匯入方式</th><th>使用方式</th><th>優點</th><th>缺點</th></tr></thead><tbody><tr><td>`import module_name`</td><td>`module_name.function()`</td><td>清楚來源，避免命名衝突</td><td>使用時需要加上模組名稱</td></tr><tr><td>`from module_name import func`</td><td>`func()`</td><td>使用簡單，僅匯入需要的內容</td><td>可能導致命名衝突</td></tr><tr><td>`import module_name as alias`</td><td>`alias.function()`</td><td>模組名稱簡潔，程式碼更易閱讀</td><td>增加了別名學習的成本</td></tr><tr><td>`from module_name import *`</td><td>`function()`</td><td>簡單直接，適用於了解所有內容的情況</td><td>容易命名衝突，降低可讀性</td></tr></tbody></table>

---

## **5. 自訂模組的匯入**

### **5.1 自訂模組**

假設你有一個名為 `mymodule.py` 的檔案，內容如下：

```python
# mymodule.py
def greet(name):
    return f"Hello, {name}!"

```

你可以在同目錄下使用：

```python
import mymodule

print(mymodule.greet("Alice"))  # 輸出: Hello, Alice!

```

### **5.2 結構化套件**

假設你有以下檔案結構：

```
myproject/
├── main.py
└── mypackage/
    ├── __init__.py
    ├── module1.py
    └── module2.py

```

`mypackage/__init__.py` 的內容：

```python
from .module1 import func1
from .module2 import func2

__all__ = ["func1", "func2"]

```

使用：

```python
from mypackage import func1

func1()

```

---

## **6. 結論**

- `import` 是 Python 中的核心功能，用於模組的重複利用。
- 掌握不同的匯入方式，可以讓你的程式碼更高效、更清晰。
- 盡量避免使用 `from module import *`，選擇明確的匯入方式，提升程式碼可讀性與維護性。

# 【Python】【__init__.py】,【 __all__】說明

### **Python 中的 `__init__.py` 與 `__all__` 用途**

在 Python 中，`__init__.py` 是專門用來初始化套件的重要檔案，而 `__all__` 是用來控制模組或套件的對外公開接口。以下將針對這兩者的用途及使用方式進行完整說明。

---

## **`__init__.py` 的用途**

### **1. 標記目錄為 Python 套件**

在 Python 3.3 之前，`__init__.py` 是標示一個目錄為套件的必要檔案。沒有這個檔案，目錄無法被視為套件。雖然 Python 3.3 之後允許隱式套件（目錄中沒有 `__init__.py` 也能被視為套件），但仍建議在目錄中保留 `__init__.py`，以明確表示該目錄是套件。

---

### **2. 套件初始化**

`__init__.py` 的內容會在匯入套件時執行，因此你可以在這裡添加初始化代碼，例如：

- 設定全域變數或環境配置。
- 匯入套件內的模組或子模組，簡化使用者匯入的流程。
- 處理套件的必要初始化邏輯。

範例：

```python
# 檔案結構
# mypackage/
# ├── __init__.py
# ├── module1.py
# ├── module2.py

# __init__.py
print("Initializing mypackage...")
from .module1 import func1
from .module2 import func2

# 匯入時會執行初始化代碼
import mypackage
# 執行結果: "Initializing mypackage..."

```

---

### **3. 控制對外公開接口**

`__init__.py` 可以用來控制使用者可以從套件匯入的功能，例如：

- 只公開指定的模組或函式。
- 隱藏內部的模組，避免使用者直接操作。

範例：

```python
# __init__.py
from .module1 import func1
from .module2 import func2

__all__ = ['func1', 'func2']

```

使用者只需匯入套件即可：

```python
from mypackage import *

func1()  # 正常執行
func2()  # 正常執行

```

---

## **`__all__` 的用途**

`__all__` 是 Python 中的特殊變數，用於定義模組或套件的對外公開接口，特別是在使用 `from module import *` 時，`__all__` 可以控制哪些成員可以被匯入。

### **1. 限制公開的內容**

`__all__` 是一個列表，用來列出所有允許匯入的成員。如果未定義 `__all__`，則預設會匯入所有不以下劃線 `_` 開頭的公開成員。

範例：

```python
# example.py
def func1():
    return "This is func1"

def func2():
    return "This is func2"

def _private_func():
    return "This is a private function"

__all__ = ['func1']  # 只允許 func1 被匯入

```

在其他程式中使用：

```python
from example import *

print(func1())  # 正常執行
print(func2())  # NameError: name 'func2' is not defined

```

---

### **2. 提升可讀性**

使用 `__all__` 可以明確標示模組或套件的公共接口，讓開發者清楚哪些功能是穩定且推薦使用的。

---

### **3. 結合套件使用**

當一個套件有多個模組時，可以在 `__init__.py` 中使用 `__all__` 控制整個套件的公共接口。

範例：

```python
# 檔案結構
# mypackage/
# ├── __init__.py
# ├── module1.py
# ├── module2.py

# module1.py
def func1():
    return "Function 1"

# module2.py
def func2():
    return "Function 2"

# __init__.py
from .module1 import func1
from .module2 import func2

__all__ = ['func1']  # 只公開 func1

```

在使用者端：

```python
from mypackage import *

print(func1())  # 正常執行
print(func2())  # NameError: name 'func2' is not defined

```

---

## **總結**

1. **`__init__.py` 的用途**：
    
    
    - 標記目錄為 Python 套件。
    - 執行套件初始化邏輯，例如匯入模組或設定全域變數。
    - 控制套件的對外接口，組織套件結構，簡化使用者的匯入流程。
2. **`__all__` 的用途**：
    
    
    - 控制 `from module import *` 時匯入的內容，避免暴露內部實現細節。
    - 提升程式碼的可讀性，讓開發者清楚哪些功能是公開的。
    - 結合 `__init__.py` 使用，控制整個套件的公共接口。

### **最佳實踐建議**

- 總是為套件添加 `__init__.py`，即使在 Python 3.3 之後不強制要求。
- 明確定義 `__all__`，只公開穩定且推薦使用的功能，隱藏內部實現細節。
- 避免使用 `from module import *`，除非必要，以提升程式碼的可讀性和穩定性。

這樣可以讓你的模組和套件更加結構化、易於維護，並避免不必要的命名衝突。

# 【Python】venv vs pyenv 比較

`venv` 與 `pyenv` 都是 Python 環境管理工具，但功能不同、用途也不同。以下是兩者的詳細比較：

---

## 🆚 `venv` vs `pyenv` 比較表

<table id="bkmrk-%E9%A0%85%E7%9B%AE-venv-pyenv-%E2%9C%85-%E5%8A%9F%E8%83%BD-%E5%BB%BA"><thead><tr><th>項目</th><th>`venv`</th><th>`pyenv`</th></tr></thead><tbody><tr><td>✅ 功能</td><td>建立虛擬環境（isolated packages）</td><td>管理多個 Python 版本（例如 3.6, 3.8, 3.11）</td></tr><tr><td>📦 主要用途</td><td>為每個專案建立獨立依賴環境</td><td>切換/安裝不同版本的 Python</td></tr><tr><td>🧰 安裝方式</td><td>Python 內建（3.3+）</td><td>需額外安裝（使用 Git clone 或安裝腳本）</td></tr><tr><td>🪄 使用方式</td><td>`python -m venv myenv`</td><td>`pyenv install 3.11.3` + `pyenv global/local`</td></tr><tr><td>📁 環境位置</td><td>專案內產生 `myenv/` 資料夾</td><td>全局安裝於 `~/.pyenv/versions/`</td></tr><tr><td>🧩 是否支援套件分離</td><td>✅ 支援（每個虛擬環境獨立）</td><td>❌ 不直接支援（但可配合 `pyenv-virtualenv`）</td></tr><tr><td>🧩 是否支援版本共存</td><td>❌ 不支援（依系統 Python）</td><td>✅ 支援多版本並快速切換</td></tr><tr><td>🔄 可搭配使用</td><td>N/A</td><td>✅ 可搭配 `pyenv-virtualenv` 使用 `venv` 功能</td></tr></tbody></table>

---

## ✅ 何時該用哪個？

### 使用 `venv`：

- 你只需在同一台機器上建立**隔離的 Python 套件環境**
- 你已安裝好適合的 Python 版本，不需要版本切換

### 使用 `pyenv`：

- 你需要安裝或切換不同版本的 Python（例如某專案需 3.6，另一個用 3.11）
- 系統預設 Python 無法修改（尤其在 macOS/Linux）
- 想搭配 `pyenv-virtualenv` 做進階管理

---

## 🧩 進階：兩者可以**一起用**

---

你可以使用 `venv` 結合安裝多個 Python 版本的方式，讓不同專案使用不同版本的 Python。以下是完整的步驟：

---

### ✅ 步驟 1：安裝多個 Python 版本

#### macOS/Linux 建議使用 [pyenv](https://github.com/pyenv/pyenv)

```bash
brew install pyenv            # macOS 安裝 pyenv
pyenv install 3.8.18          # 安裝 Python 3.8.18
pyenv install 3.11.9          # 安裝 Python 3.11.9

```

#### Windows 使用 [Python 官網](https://www.python.org/downloads/) 安裝多版本，並記住安裝路徑。

---

### ✅ 步驟 2：為專案建立虛擬環境並指定 Python 版本

#### 使用 `pyenv` 時

```bash
pyenv shell 3.8.18
python -m venv venv38         # 使用 3.8.18 建立虛擬環境

```

#### 使用絕對路徑（不使用 pyenv）

```bash
# 假設你已安裝 Python 3.11 在 /usr/local/bin/python3.11
/usr/local/bin/python3.11 -m venv venv311

```

---

### ✅ 步驟 3：啟用虛擬環境

```bash
source venv38/bin/activate    # macOS/Linux
venv38\Scripts\activate.bat   # Windows CMD

```

啟用後，你執行 `python` 將會使用虛擬環境中的 Python 版本。

---

### ✅ 步驟 4：確認 Python 版本

```bash
python --version

```

---

以下是將 **多 Python 版本虛擬環境與開發工具整合** 的常見場景與操作：

---

### 🧩 1. 在 **VS Code** 中使用不同版本虛擬環境

#### 📌 步驟：

1. 先建立虛擬環境（如前述）
2. 開啟 VS Code 該專案資料夾
3. 左下角點選 Python 解譯器版本
4. 選擇你剛建立的虛擬環境（例如 `.venv/bin/python` 或 `venv/Scripts/python.exe`）

#### 📁 小技巧：

你也可以在專案內加一個 `.vscode/settings.json`：

```json
{
  "python.pythonPath": "venv38/bin/python"
}

```

---

### 🧩 2. pipx 搭配多 Python 版本

`pipx` 可以讓你用不同版本 Python 安裝 CLI 工具，保持環境乾淨。

```bash
pipx install some-tool --python python3.11

```

---

### 🧩 3. poetry 管理虛擬環境與依賴（推薦進階用戶）

```bash
poetry env use /usr/local/bin/python3.11
poetry install

```

它會自動為專案建立 `.venv` 並鎖定依賴版本。

---

### 🧩 4. 為不同目錄自動切換 Python 版本（pyenv local）

```bash
cd my-project38
pyenv local 3.8.18

cd ../my-project311
pyenv local 3.11.9

```

`pyenv` 會在目錄中寫入 `.python-version`，自動切換。

---

如果你告訴我你的作業系統（macOS/Linux/Windows）與偏好（如 VS Code/CLI），我可以給你量身打造的設定方式。要嗎？

# 【Python】套件安裝

<span style="background-color: #fbeeb8;">**\# 羅列需要安裝的 Python 套件**</span>

\--&gt; 寫在 requirements.txt 檔

<span style="background-color: #fbeeb8;">**\# requirements.txt** </span>

```
requests
robotframework
robotframework-requests
robotframework-seleniumlibrary
robotframework-jsonlibrary
urllib3
idna
certifi
charset-normalizer
jsonpath-ng
```

#### **Windows 安裝**

<span style="background-color: #fbeeb8;">**\# Python 套件安裝執行檔**</span>

\--&gt; requirements.txt 放在同一層目錄 (建議放在D槽目錄，Ｃ槽有時候會有權限問題造成安裝失敗)

\--&gt;點兩下執行安裝

<span style="background-color: #fbeeb8;">**\# install\_python\_pip.bat** </span>

```shell
@echo off

setlocal
set INDEX_URL=http://10.10.10.1:8080/repository/python_group/simple/
set TRUSTED_HOST=10.10.10.1

REM #安裝套件
REM #如果使用私有repo
REM # pip install -r requirements.txt --index-url %INDEX_URL% --trusted-host %TRUSTED_HOST%

REM #連至外網安裝(擇一)
pip install -r requirements.txt

echo All packages installed.

pause
```

#### **Linux 安裝**

**<span style="background-color: #fbeeb8;">\# install\_python\_pip.sh </span>**

```shell
#!/bin/bash

# 設定 pip 私有套件來源
INDEX_URL="http://10.10.10.1:8080/repository/python_group/simple/"
TRUSTED_HOST="10.10.10.1"

# 安裝套件
# 如果使用私有repo
# pip install -r requirements.txt --index-url "$INDEX_URL" --trusted-host "$TRUSTED_HOST"

# 外網安裝(擇一)
pip install -r requirements.txt

echo "All packages installed."
```

```shell
# 放在同一目錄下執行
bash install_python_pip.sh 
```

<span style="background-color: #fbeeb8;">**\# 列出所有已安裝套件**</span>

**1. 打開 VSCode**

**2. Terminal 下指令**

```
pip list
```

```
$pip list
Package    Version
---------- -------
pip        23.0.1
setuptools 58.1.0
```

# React

# 【React】建立React 環境

引入js

```HTML
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

```

建立根物件

```HTML
<div id="root"></div>
```

渲染Dom物件

[![image-1674565801517.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-01/scaled-1680-/F5eafezONaoghQJO-image-1674565801517.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-01/F5eafezONaoghQJO-image-1674565801517.png)

```JavaScript
<script type="text/babel">
  function App(){
    return <h1>Hello React</h1>
  }

  const el = document.getElementById("root");
  const root = ReactDOM.createRoot(el);
  root.render(<App />);
 /* 簡易寫法
  ReactDOM
     .createRoot(el)
     .render(<App />);
  */
</script>
```

# 【React】資料綁定

### 單向綁定

##### 使用大括弧綁定變數，標籤內屬性不用加雙引號

```JavaScript
const data = {
  imgUrl : "https://images.unsplash.com/photo-1650190558669-e8cc1ae3551f?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1770&q=80",
  titl: "AAAA",
  content : "Lorem ipsum dolor sit amet consectetur adipisicing elit. Eius, esse sequi? Dolores, perspiciatis! Minima aliquid tempore placeat ut qui atque asperiores totam iusto, dolorem est blanditiis nesciunt minus. Officiis, voluptate!",
  link: "https://www.google.com.tw"
}

function App(){
  
  return <div className="card">
  <img src={data.imgUrl} className="card-img-top" alt="..." />
    <div className="card-body">
      <h5 className="card-title">{data.titl}</h5>
      <p className="card-text">{data.content}</p>
      <a href={data.link} className="btn btn-primary">Go somewhere</a>
    </div>
  	{ /** 可用註解 */}
    { (function(){return "可function"})() }
  	{ tt ? tt : "可用三元運算" }
    { /** 不可用物件但可用 JSON.stringify 轉為字串除錯 */}
    { JSON.stringify(data)}
    { /** true, false, undefined, null 不會顯示 */}
    { [1,2,3] /** 純值陣列會直接顯示 ex 1 2 3 */}
  </div>
}
```

### 綁定陣列

```JavaScript
function App() {
  const data = {
    imageUrl: 'https://images.unsplash.com/photo-1564564321837-a57b7070ac4f?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2352&q=80',
    name: '卡斯伯',
    description: 'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Modi culpa aspernatur tempore cupiditate illo minima voluptatem blanditiis optio accusantium earum voluptate magni repellendus, ipsa dolor adipisci libero qui commodi veritatis.',
    link: 'https://www.casper.tw/'
  }

  // #3 直接插入 HTML 結構的變數
  // https://getbootstrap.com/docs/5.2/components/card/#list-groups
  let list = [
    <li className="list-group-item">An item</li>,
    <li className="list-group-item">A second item</li>,
    <li className="list-group-item">A third item</li>,
  ];
  return <div className="card w-50">
    <img src={data.imageUrl} className="card-img-top" alt="..." />
    <div className="card-body">
      <h5 className="card-title">{ data.name }</h5>
      <p className="card-text">{ data.description }</p>
      <a href={data.link} target="_blank" className="btn btn-primary">Go somewhere</a>
    </div>
    {/* 2. 陣列中的值會直接呈現在畫面上 */}
    <ul className="list-group list-group-flush">
      {list}
    </ul>
  </div>
}
```

### 綁定Style

#### 兩個大括號包住 style = {{}}

```HTML
<!-- 原html-->
<div class="card" style="width: 18rem">
  <div
    class="card-img-top"
    style="
      height: 200px;
      background-image: url('https://images.unsplash.com/photo-1564564321837-a57b7070ac4f?ixlib=rb-1.2.1&amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;auto=format&amp;fit=crop&amp;w=2352&amp;q=80');
      background-size: cover;
      background-position: center center;
    "
  ></div>
  <div class="card-body">
    <h5 class="card-title">卡斯伯</h5>
    <p class="card-text">
      Lorem ipsum dolor sit amet consectetur, adipisicing elit. Modi culpa
      aspernatur tempore cupiditate illo minima voluptatem blanditiis optio
      accusantium earum voluptate magni repellendus, ipsa dolor adipisci libero
      qui commodi veritatis.
    </p>
    <a href="https://www.casper.tw/" target="_blank" class="btn btn-primary"
      >Go somewhere</a
    >
  </div>
</div>
```

```JavaScript
/**
有 - 要改為駝峰式命名
background-image => backgroundImage 
*/
return (
      <div className='card' style={{width: "18rem"}}>
        <div className='card-img-top' style={{
          height: '200px',
          backgroundImage: `url('https://images.unsplash.com/photo-1564564321837-a57b7070ac4f?ixlib=rb-1.2.1&amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;auto=format&amp;fit=crop&amp;w=2352&amp;q=80')`,
          backgroundSize: 'cover',
          backgroundPosition: "center center"
        }}>
        </div>
        <div className='card-body'>
          <h5 className='card-title'>{data.name}</h5>
          <p className='card-text'>{data.description}</p>
          <a href={data.link} target='_blank' className='btn btn-primary'>
            Go somewhere
          </a>
        </div>
      </div>
    );
```

# 【React】JSX 如何轉譯

```JavaScript
// JSX 寫法
function App() {
   return <h1>React 我來了 <small className="text-danger">{new Date().toLocaleDateString() }</small></h1>
}

// JS 寫法 React.createElement
function App(){
  // React.createElement({tag} , {屬性}, {值}
  return React.createElement('h1',null, "React 我來了",
  React.createElement(
    'small',
    {
      className :"text-danger"
    },new Date().toLocaleDateString())
  );
}
```

# 【React】JSX 開發常見問題

<div id="bkmrk-1.-%E5%AE%9A%E7%BE%A9%E5%85%83%E4%BB%B6%E4%B8%80%E5%AE%9A%E6%98%AF%E6%89%8B%E5%AD%97%E5%A4%A7%E5%AF%AB">1. 定義元件一定是手字大寫</div>```JavaScript
/** wrong*/
function app(){
}
/** correct*/
function App(){
}
```

<div id="bkmrk-2.-%E5%A4%9A%E5%80%8B%E5%85%83%E7%B4%A0%E5%A4%96%E5%B1%A4%E9%9C%80%E8%A6%81%E4%BD%BF%E7%94%A8%E6%A8%99%E7%B1%A4%E5%8C%85%E8%A6%86%EF%BC%8C%E6%88%96%E4%BD%BF">2. 多個元素外層需要使用標籤包覆，或使用 React.Fragment</div>```JavaScript
// wrong 
function App(){
   // return 後沒有東西預設會補; => 顯示錯誤
	return 
  	<div>
      .....
    </div>
}
// correct
function App(){
	return <div>
      ......
    </div>
}

```

```JavaScript
// 不想包div 用 React.Fragment
function App(){
	return <React.Fragment>
      ......
    </React.Fragment>
}
    
// 同上    
function App(){
	return <>
      ......
    </>
}


```

<div id="bkmrk-3.-%E6%B2%92%E6%9C%89%E6%AD%A3%E7%A2%BA%E7%B5%90%E5%B0%BE%EF%BC%8C%E7%B5%90%E5%B0%BE%E4%B8%8D%E6%AD%A3%E7%A2%BA">3. 沒有正確結尾，結尾不正確</div>```HTML
<!-- wrong --> 
<input class="form-control" list="datalistOptions" 
       id="exampleDataList" placeholder="Type to search...">

<!-- correct --> 
<input className="form-control" list="datalistOptions" 
       id="exampleDataList" placeholder="Type to search..." />

```

<div id="bkmrk-4.-%E5%B1%AC%E6%80%A7%E8%BD%89%E6%8F%9B%E5%90%8D%E7%A8%B1%E9%81%8B%E7%94%A8%E8%A6%81%E6%AD%A3%E7%A2%BA">4. 屬性轉換名稱運用要正確</div>```HTML
<!-- wrong-->
<label htmlFor="exampleDataList" className="form-label" style="color: var(--bs-blue)">
  Datalist example
</label>
<!-- correct-->
<label htmlFor="exampleDataList" className="form-label" style={{color: 'var(--bs-blue)'}}>
  Datalist example
</label>
```

<div id="bkmrk-"></div><div id="bkmrk-5.-return-%E5%BB%BA%E8%AD%B0%E5%8A%A0%E5%85%A5%E6%8B%AC%E8%99%9F">5. return 建議加入括號</div>```JavaScript
// wrong 
function App(){
   // return 後沒有東西預設會補; => 顯示錯誤
	return 
  	<div>
      .....
    </div>
}
// correct
function App(){
	return (<div>
      ......
    </div>)
}

```

# 【React】JSX 與 Html 標籤屬性

```JavaScript
<script type="text/babel">
  function App() {
    const htmlTemplate = { __html :'<div>這裡有一段文字</div>'}

    return (
      <div>
        {/*  使用 dangerouslySetInnerHTML 顯示html */}
        <div dangerouslySetInnerHTML={htmlTemplate}></div>

        {/* class => className */}
        <div><button type="button" className="btn btn-primary">Primary</button></div>

        {/* checked => defaultChecked 與 結尾標籤 */}
        <div><input type="checkbox" defaultChecked /></div>

        {/* value => defaultValue */}
        <div><input type="text" defaultValue="卡斯伯" /></div>

        {/* for => htmlFor */}
        <div>
          <label htmlFor="email">請輸入 Email</label>
          <input type="email" id="email" />
        </div>

        {/* selected => defaultValue */}
        <div>
          <select name="" id="" defaultValue="2">
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
          </select>
        </div>

        { /* textarea 文字 要使用 defaultValue*/ }
        <div>
          <textarea name="" id="" cols="30" rows="5" defaultValue="這裡可以放多行文字"></textarea>
        </div>

        {/* onChange */}
        <div><input type="text" onChange={
          function(event){
            console.log(event.target.value);
          }
        } /></div>
      </div>
    );
  }

  const el = document.getElementById('root');
  const root = ReactDOM.createRoot(el);
  root.render(<App />);
```

# 【React Admin】定時refresh

React Admin 是一個基於 React 框架的開源後台管理界面框架。若要定時刷新 React Admin 的數據，可以使用 React 的 useEffect 鉤子來定時刷新。

以下是一個示例代碼：

```JavaScript
import React, { useState, useEffect } from 'react';
import { Admin, Resource, ListGuesser } from 'react-admin';
import jsonServerProvider from 'ra-data-json-server';

const dataProvider = jsonServerProvider('https://jsonplaceholder.typicode.com');

const App = () => {
  const [refreshInterval, setRefreshInterval] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setRefreshInterval(prevRefreshInterval => prevRefreshInterval + 1);
    }, 10000); // 10 秒刷新一次

    return () => {
      clearInterval(interval);
    };
  }, []);

  return (
    <Admin dataProvider={dataProvider}>
      <Resource name="posts" list={ListGuesser} refresh={refreshInterval} />
    </Admin>
  );
};

export default App;

```

在這個示例中，我們使用 useState 鉤子來保存刷新間隔時間，然後使用 useEffect 鉤子來啟動定時器，每10秒刷新一次。我們還在 Resource 中傳遞了 refreshInterval，它將在 useState 中更新，這樣它會觸發 Resource 的重新加載。

需要注意的是，經常性的刷新會增加服務器負載，並且可能會降低應用程序的性能。因此，應該謹慎使用定時刷新，並根據實際需要進行調整。

# VM相關

# 【Proxmox】Fatal glibc error: CPU does not support x86-64-v2

[https://foxi.buduanwang.vip/linux/2168.html/](https://foxi.buduanwang.vip/linux/2168.html/)

rhel9 開始需要CPU支持SSE4.2，SSSE3參考

[為x86-64-v2 微架構級別構建紅帽企業Linux 9 |紅帽開發人員(redhat.com)](https://developers.redhat.com/blog/2021/01/05/building-red-hat-enterprise-linux-9-for-the-x86-64-v2-microarchitecture-level)

Proxmox VE上默認的kvm64並沒有相應的指令集，所以會發生內核崩潰。

修改集群CPU文件`/etc/pve/virtual-guest/cpu-models.conf`

```
cpu-model: rhel9
  flags +ssse3;+sse4_1;+sse4_2;+popcnt
  phys-bits host
  hidden 0
  hv-vendor-id proxmox
  reported-model kvm64
```

將CPU模型改成`rhel9`

[![image-1682488296710.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-04/scaled-1680-/ssrnP7WbEl80mqPp-image-1682488296710.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-04/ssrnP7WbEl80mqPp-image-1682488296710.png)

``

# Vagrant   相關

https://developer.hashicorp.com/vagrant/downloads

# VSCode

# 【extension】 自訂extenstion 位置

### vscode extenstions 儲存位置

#### <span style="background-color: #ffff00;">C:\\Users\\使用者名稱\\.vscode</span>

#### 想要自定位置－在捷徑上新增 --extensions-dir 設定extenstion 位置

[ ![image-1675391800393.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-02/scaled-1680-/Rsd0UzUWOuQOhaU9-image-1675391800393.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-02/Rsd0UzUWOuQOhaU9-image-1675391800393.png)

# 【extension】安裝

#### 搜尋安裝

[![image-1633423731590.png](http://bookstack.treemanou.com/uploads/images/gallery/2021-10/scaled-1680-/XzUOAzvBB5ouDr7d-image-1633423731590.png)](http://bookstack.treemanou.com/uploads/images/gallery/2021-10/XzUOAzvBB5ouDr7d-image-1633423731590.png)

#### VSIX安裝(檔案安裝)

網路連線有問題，無法自動安裝的時候可以手動下載

[![image-1633424029080.png](http://bookstack.treemanou.com/uploads/images/gallery/2021-10/scaled-1680-/pIoIS9bIbG6InC4V-image-1633424029080.png)](http://bookstack.treemanou.com/uploads/images/gallery/2021-10/pIoIS9bIbG6InC4V-image-1633424029080.png)

從VSIX檔案安裝

[![image-1633423824775.png](http://bookstack.treemanou.com/uploads/images/gallery/2021-10/scaled-1680-/3KTggDjjfc0QBOii-image-1633423824775.png)](http://bookstack.treemanou.com/uploads/images/gallery/2021-10/3KTggDjjfc0QBOii-image-1633423824775.png)

#### **批次安裝**

**<span style="background-color: #fbeeb8;">\# 羅列需要安裝的套件</span>**

\--&gt;寫在 extensions.txt

<span style="background-color: #fbeeb8;">**\# extensions.txt** </span>

```
d-biehl.robotcode
eamodio.gitlens
github.copilot
github.copilot-chat
mhutchie.git-graph
ms-python.debugpy
ms-python.python
ms-python.vscode-pylance
```

**<span style="background-color: #fbeeb8;">\# 若要將現有套件可用語法匯出（用來統一最新已安裝套件盤點）</span>**

```shell
code --list-extensions > extensions.txt
```

\# Extensions ID --&gt; 可從界面上的小齒輪複製

[![image-1750820507718.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-06/scaled-1680-/uf576vlo4QrPYiFk-image-1750820507718.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-06/uf576vlo4QrPYiFk-image-1750820507718.png)

#### **Windows安裝**

**<span style="background-color: #fbeeb8;">\# VSCode Extensions 套件安裝執行檔</span>**

\--&gt; extensions.txt 放在同一層目錄

\--&gt;點兩下執行安裝

<span style="background-color: #fbeeb8;">**\# install\_vs\_extensions.bat** </span>

```shell
@echo off
for /F %%i in (extensions.txt) do (
    echo Installing %%i ...
    code --install-extension %%i
)
echo All extensions installed.
pause
```

#### **Linux安裝**

<span style="background-color: #fbeeb8;">**\# install\_vs\_extensions.sh**</span>

\--&gt; extensions.txt 放在同一層目錄

\--&gt; bash install\_vs\_extensions.sh

```shell
#!/bin/bash

# 從 extensions.txt 逐行讀取 VS Code 擴充套件並安裝
while IFS= read -r extension; do
    echo "Installing $extension ..."
    code --install-extension "$extension"
done < extensions.txt

echo "All extensions installed."

```

如果出現code 指令無法辨認，須先將[vscode 加入環境變數](https://bookstack.treemanou.com/books/treeman/page/vscode-class-path)

# 【extension】推薦

#### 專案管理

[![image-1633423633855.png](http://bookstack.treemanou.com/uploads/images/gallery/2021-10/scaled-1680-/vHhPk1p1vVRuO9n9-image-1633423633855.png)](http://bookstack.treemanou.com/uploads/images/gallery/2021-10/vHhPk1p1vVRuO9n9-image-1633423633855.png)

#### 同步原端資料  
[https://github.com/Natizyskunk/vscode-sftp](https://github.com/Natizyskunk/vscode-sftp)

[![image-1633423190973.png](http://bookstack.treemanou.com/uploads/images/gallery/2021-10/scaled-1680-/mJVq6yuSe8d5XJ7l-image-1633423190973.png)](http://bookstack.treemanou.com/uploads/images/gallery/2021-10/mJVq6yuSe8d5XJ7l-image-1633423190973.png)

#### React 開發相關

[![image-1679882791034.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-03/scaled-1680-/QD59uCdy4VFNv1PW-image-1679882791034.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-03/QD59uCdy4VFNv1PW-image-1679882791034.png)

# 【extension】Code Runner

### 【錯誤處理】列印中文會顯亂碼

【解決】將輸出至終端機打勾

[![image-1657252347905.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/scaled-1680-/wCWbjfVUsqWgGmKz-image-1657252347905.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/wCWbjfVUsqWgGmKz-image-1657252347905.png)

# 【extension】ProjectManager

[![image-1638770347187.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/ttfwvNFWPFprId3R-image-1638770347187.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/ttfwvNFWPFprId3R-image-1638770347187.png)

```JSON
// "name": 顯示名稱,
// "rootPath": 本機資料夾("\" 要寫成 "\\"),
// "tag": tag分類,
// 是否顯示
[
	{
      "name": "go_demo",
      "rootPath": "D:\\go_workspace\\go_demo",
      "tags":["go","test"],
      "enabled": true
	},{
       "name": "go_demo2",
      "rootPath": "D:\\go_workspace\\go_demo2",
      "tags":["go","test"],
      "enabled": true
	}
 ]
```

使用 tag / list 排序

[![image-1638774516996.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/UJdkcx9a2dLjmQmr-image-1638774516996.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/UJdkcx9a2dLjmQmr-image-1638774516996.png)

[![image-1638774582569.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/F3GGbtR6k9eBMgkI-image-1638774582569.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/F3GGbtR6k9eBMgkI-image-1638774582569.png)

# 【extension】sftp

### 產生Config

vscode 按下 Ctrl + Shift + P

輸入 SFTP:Config

---

[![image-1638770235855.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/tNIi6QkOATYpcsHp-image-1638770235855.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/tNIi6QkOATYpcsHp-image-1638770235855.png)

### Config 相關

---

單一來源

```JSON
[
  {
    "name": "demo_10.1.1.1",
    "context": "D:\\php_workspace\\demo",
    "host": "10.1.1.1",
    "username": "user1",
    "password": "12345678",
    "remotePath": "/root"
	}
]
```

多個來源

```JSON
[
  {
    "name": "demo_10.1.1.1",
    "context": "D:\\php_workspace\\demo\web1",
    "host": "10.1.1.1",
    "username": "user1",
    "password": "12345678",
    "remotePath": "/root"
	},
     {
    "name": "demo_10.1.1.2",
    "context": "D:\\php_workspace\\demo\web2",
    "host": "10.1.1.2",
    "username": "user1",
    "password": "12345678",
    "remotePath": "/root"
	},
]
```

使用跳板機(帳號密碼)

```JSON
[
{
    "name": "jump_demo",
    "remotePath": "/root",
    "context": "D:\\php_workspace\\jump_demo",
    
    "host": "10.1.1.1", 	// 跳板機ip
    "username": "user1",	// 跳板機 帳號
    "password": "12345678", // 跳板機 密碼
    
    "hop": {
        "host": "192.168.1.1", // 目的地ip
        "port": 22,
        "username": "target_user",	// 目的地 帳號
        "password": "66778899"		// 目的地 密碼
    },
    // 排除檔案
    "ignore": [
        ".vscode",
        ".git",
        ".DS_Store",
        "**/logs/**",
        "**/sessions/**"
    ],
  	// 儲存後自動上傳
    "uploadOnSave": false
}
]
```

使用跳板機(憑證)

```JSON
[
{
    "name": "jump_demo",
    "remotePath": "/root",
    "context": "D:\\php_workspace\\jump_demo",
    
    "host": "10.1.1.1", // 跳板機ip
    "username": "user1",
    "password": "12345678",   
    
    "hop": {
        "host": "192.168.1.1", // 目的地ip
        "port": 22,
        "username": "target_user",
        "privateKeyPath": "/home/user1/.ssh/id_rsa" //使用憑證(跳板機位置)
    },
    // 排除檔案
    "ignore": [
        ".vscode",
        ".git",
        ".DS_Store",
        "**/logs/**",
        "**/sessions/**"
    ],
  	// 儲存後自動上傳
    "uploadOnSave": false
}
]
```

第一次設定完，可以按下【Ctrl + Shift + P 】(Mac: Cmd + Shift + P)

鍵入 &gt;SFTP:Sync Remote -&gt; Local

先將遠端資料同步至本機

[![image-1657186191829.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/scaled-1680-/zOtmW2iuNdnjImut-image-1657186191829.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/zOtmW2iuNdnjImut-image-1657186191829.png)

---

### 瀏覽遠端檔案/選取Server檔案至本地編輯

選取左側icon

[![image-1657186997112.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/scaled-1680-/TfAGHB74KNUTyp4L-image-1657186997112.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/TfAGHB74KNUTyp4L-image-1657186997112.png)

，展開目錄找到要編輯檔案，【Edit in Local】

[![image-1657186787174.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/scaled-1680-/dYCIqlAbr2GaUdNP-image-1657186787174.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/dYCIqlAbr2GaUdNP-image-1657186787174.png)

[![image-1657186848499.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/scaled-1680-/Cvf7ePo36wLHdG1l-image-1657186848499.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/Cvf7ePo36wLHdG1l-image-1657186848499.png)

---

### 檔案比較

在本地編輯的時候，可以按右鍵【Diff with Remote】與server 檔案比較

[![image-1657186402680.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/scaled-1680-/gQD3mLHQYxZVPA3B-image-1657186402680.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/gQD3mLHQYxZVPA3B-image-1657186402680.png)

---

###  

###  

---

### 上傳，下載檔案

可以按右鍵，【Upload/ Download】上傳本地檔案至Server 或是 下載Server 檔案

[![image-1657186492797.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/scaled-1680-/1YDtrGQUAJ1ZCsE6-image-1657186492797.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-07/1YDtrGQUAJ1ZCsE6-image-1657186492797.png)

---

# 【extention】RemoteSSH

# Running the contributed command: ‘\_workbench.downloadResource‘ failed解决

1 問題描述  
此前，本人參考網上教程在VS Code中配置了“Remote SSH”插件（比如 但是今天使用遇到了問題Error: Running the contributed command: '\_workbench.downloadResource' failed

  
2 解決方案  
刪除Remote Server上的~/.vscode-serverrm -rf ~/.vscode-server  
在本地的VS Code中再次嘗試連接，毫無意外，這次依然是失敗的。但是這一步會在遠程服務器中生成~/.vscode-server/bin/<span style="color: #ff0000;">xxx</span>/vscode-server.tar.gz

上面的<span style="color: #ff0000;">xxx</span>的文件夾稱為Commit Id，現在利用Commit ID下載遠程連接需要的文件。

然後使用這個鏈接：https://update.code.visualstudio.com/commit:<span style="color: #ff0000;">xxx</span>/server-linux-x64/stable下載所需文件，注意鏈接中的$COMMIT\_ID這幾個字符（注意$不要忘記）需要換成自己的，比如我的是2d23c42a936db1c7b3b06f918cde29561cc47cd6，  
下載完畢後，將下載的文件upload到Remote Server的~/.vscode-server/bin/xxx

tar -xvf stable --strip-components 1

然後使用chmod +x node server.sh

再次使用VS Code嘗試鏈接，這一步會鏈接成功

  
原文链接：https://blog.csdn.net/ibless/article/details/118610776

# 【tip】修改設定檔(settings.json)

圖形化介面

[![image-1638780167411.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/eY6yD9zm4EaE6Etw-image-1638780167411.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/eY6yD9zm4EaE6Etw-image-1638780167411.png)

修改 setting.json

[![image-1638780258387.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/wqT15iKBaRVx01oo-image-1638780258387.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/wqT15iKBaRVx01oo-image-1638780258387.png)

[![image-1638780282253.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/tqsPhkFcvdvFWOXV-image-1638780282253.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/tqsPhkFcvdvFWOXV-image-1638780282253.png)

# 【tip】常用settings

修改 settings.json

```JSON
{
"workbench.editor.wrapTabs": true, 			// tab數量超過一行是否摺疊顯示(false 會出現scroll bar)
"workbench.editor.enablePreview": false, 	// 點選檔案是否使用同一個tab(不另開)
  
 "editor.defaultFormatter": null, 			// 預設排版引擎
  "[html]": {								// html 使用排版引擎
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "editor.links": false, 					// 關閉超連結(避免誤點超連結，選用)
}
```

---

editor.links

[![image-1665730176972.png](https://bookstack.treemanou.com/uploads/images/gallery/2022-10/scaled-1680-/yrQRQqTCjs2EZa1z-image-1665730176972.png)](https://bookstack.treemanou.com/uploads/images/gallery/2022-10/yrQRQqTCjs2EZa1z-image-1665730176972.png)

# 【tip】format 程式碼排版

快速鍵

```
在Windows Shift + Alt + F
在Mac Shift + Option + F
在Ubuntu Ctrl + Shift + I
```

存檔自動格式化(設定 Ctrl + ,)

[![image-1638779536748.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/gdvaklh9cxxBEdaa-image-1638779536748.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/gdvaklh9cxxBEdaa-image-1638779536748.png)

如何修改預設排版外掛

[修改 setting.json](https://bookstack.treemanou.com/books/treeman/page/tipsettingjson) (複製後請將註解刪除，避免json格式錯誤)

```JSON
{
    // 預設formatter引擎
  	"editor.defaultFormatter": null,
  	// javascript 排版使用引擎
    "[javascriptreact]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    },
}
```

# 【extention】Preettier

[![image-1704336769236.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-01/scaled-1680-/ZctIc0knYhj8ynPd-image-1704336769236.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-01/ZctIc0knYhj8ynPd-image-1704336769236.png)

# 【 VScode】 加入class path

了解，你的意思是想讓 `code` 指令（用來從終端機開啟 VS Code）**在 Zsh 中可以使用**。這通常是因為 `code` 沒有被加入 PATH。以下是解法：

---

### ✅ macOS 上讓 `code` 指令可用（官方方式）

1. 打開 VS Code
2. 用快捷鍵 `Cmd + Shift + P` 打開 **Command Palette**
3. 輸入並選擇：
    
    ```
    Shell Command: Install 'code' command in PATH
    
    ```
4. 關閉終端機重新開啟，輸入：
    
    ```bash
    code --version
    
    ```
    
    若能顯示版本就成功了！

---

### ✅ 手動加入 `code` 到 PATH（若上面方式無效）

VS Code 的 `code` 通常在這裡：

```bash
/Applications/Visual\ Studio\ Code.app/Contents/Resources/app/bin

```

你可以將這段加入 `~/.zshrc`：

```zsh
export PATH="$PATH:/Applications/Visual Studio Code.app/Contents/Resources/app/bin"

```

然後執行：

```bash
source ~/.zshrc

```

接著執行 `code` 應該就會有反應了。

# Vue

# 【Vue】相關連結

- [前言 | 重新認識 Vue.js | Kuro Hsu](https://book.vue.tw/preface.html#%E7%82%BA%E4%BB%80%E9%BA%BC%E8%A6%81%E5%AF%AB%E9%80%99%E6%9C%AC%E6%9B%B8)
- [\[ 重構倒數30天，你的網站不Vue白不Vue \] :: 2021 iThome 鐵人賽](https://ithelp.ithome.com.tw/users/20125854/ironman/4112)
- [Vue.js 進階心法 :: 2021 iThome 鐵人賽](https://ithelp.ithome.com.tw/users/20107637/ironman/4510)
- [介绍 | Vue Router (vuejs.org)](https://router.vuejs.org/zh/introduction.html)

---

- [【UI】Why Quasar? | Quasar Framework](https://quasar.dev/introduction-to-quasar)
- [【UI】Element Plus (element-plus.org)](https://element-plus.org/en-US/#/zh-CN)
- [【UI】vuetifyjs](https://vuetifyjs.com/)

---

# vue

```
routes:[
        {
            path: '/login',
            name: 'login',
            component: loginPage,
            beforeEnter: (to, from, next) => {
                Store.dispatch('role/getMe').then(response => {
                    next()
                })
            }
        },
]



===========================================================================



import Vue from 'vue'
import Vuex from 'vuex'
import sms from './modules/sms'
import role from './modules/role'
import group from './modules/group'
import permission from './modules/permission'

Vue.use(Vuex)

const store = new Vuex.Store({
    modules: {
        sms: sms,
        role: role,
        group: group,
        permission: permission
    }
})

export default store



===========================================================================



import axios from 'axios'

const state = {
    permissionList: [],
    permissionDropdownList: [],
    permissionDetail: {}
}

const actions = {
    async getList ({ commit }, params) {
        try {
            const { data: { data, total } } = await axios.get(`api/permissions`, { params })
            commit('SET_PERMISSION_LIST', {
                data,
                total: total
            })
        } catch (err) {
            console.log('get list', err)
        }
    },

    async getDropdownList ({ commit }, params) {
        try {
            const { data: { data, total } } = await axios.get(`api/permissions/dropdownlist`, { params })
            commit('SET_PERMISSION_DROPDOWNLIST', {
                data,
                total: total
            })
        } catch (err) {
            console.log('get dropdown list', err)
        }
    },

}

const mutations = {
    SET_PERMISSION_LIST (state, permissions) {
        state.permissionList = permissions.data
    },
    SET_PERMISSION_DROPDOWNLIST (state, permissions) {
        state.permissionDropdownList = permissions.data
    },
    SET_PERMISSION_DETAIL (state, permission) {
        state.permissionDetail = permission.data.data
    },
}

export default {
    namespaced: true,
    state,
    mutations,
    actions
}

```

# Vue 建立專案

```shell
# install vue cli
npm install -g @vue/cli

# 建立 my-project 專案
vue create my-project
```

選第三個

[![image-1639309733425.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/YUPzMeOFkomgzycR-image-1639309733425.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/YUPzMeOFkomgzycR-image-1639309733425.png)

按下空白選取或是取消組件，Enter

[![image-1639309818548.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/qFa6it1gDojBszWB-image-1639309818548.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/qFa6it1gDojBszWB-image-1639309818548.png)

選擇vue版本 -&gt; 3.X

[![image-1639309891104.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/Zdx2f4Pim4V8kc6V-image-1639309891104.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/Zdx2f4Pim4V8kc6V-image-1639309891104.png)

Yes -&gt; Sass/SCSS( with node-sass) -&gt; Inpackage.json -&gt; Yes -&gt; Enter

[![image-1639310052473.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/rPL3QOEEgG9ehfYU-image-1639310052473.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/rPL3QOEEgG9ehfYU-image-1639310052473.png)

進入目錄 -&gt; 起測試服務

```shell
# 進入專案目錄
cd vue-demo
# 起測試服務
npm run serve
```

[![image-1639310212824.png](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/scaled-1680-/wT37lPD4O1dpgrte-image-1639310212824.png)](https://bookstack.treemanou.com/uploads/images/gallery/2021-12/wT37lPD4O1dpgrte-image-1639310212824.png)

# YAML 簡介

# YAML 簡介

YAML 格式中，含有一個到多個 **節點 (node)**，每個節點必定是 **純量 (Scalar)**、**序列 (Sequence)**、**映射(Mapping)資料** 其中一種。

- 純量 (scalar)
    
    運用零個或多個 Unicode 字元，以表示 **自定義資料 (opaque datum)**。說白一點，就是將**最基本的、不可再分的值**，全部使用文字來描述。
- 序列 (sequence)
    
    有順序的節點清單。清單內容，不限制節點的類型。以破折號( “**`-`** ”)，表示該行資料為序列。

```YAML
- 20
- 'Two'
- 34.3
```

- 映射 (mapping)

使用 Key: Value 的配對方式，以便快速找到所要的資料。 Key 是索引值，所以 Key 必需是 **唯一值**。  
以 冒號加空格("`:`") 的方式，來標註 key: value 的組合。

```YAML
language: csharp
```

## YAML 語法

在 YAML 的區塊集(Block collection) 中，以 **空格** **縮行** 的方式，表示資料的階層。  
**左側對齊的節點，視為同一階層的資料**。  
因為 `Tab` 可能在不同語言，表示方式不同，所以 **YAML 不建議使用 `Tab` 作為縮行的方式**。

YAML 的語法中，支援 **註解** 的標註，在 `#` 後方的文字，都會視為註解內容。

```YAML
# 註解
```

##### 序列: scalars

```
- John
- Roy
```

##### 序列: mapping

```
-
  name: John
  age: 20
  
-
  name: Roy
  age: 32
```

##### 映射: Scalars to Scalars

```
name: John
age: 20
```

##### 映射: Scalars to Sequence

```
person: 
  - John
  - Roy
```

### 02. 區塊的結構

有時，可能需要針對 YAML 內的資料，進行區塊的劃分。

YAML 運用三個破折號( “`---`“) ，表示 `---` 下方的，是一個新的資料區塊。並以三個點號("'`...`")，表示該區塊的結束。

```YAML
---	# begin

... # end
```

### 03. 多行資料的表示方式

有時，當 Scalars 內的文字太長或是需要分段落時，會以 `|` 或 `>` 表示。

- `|` 表示後面的文字，**每一行資料都視為獨立的資料**。

```YAML
node: |
    This is a book.
    it is a pen.
```

取回的 node 資料結果，如下

```
This is a book.
it is a pen.
```

- `>` 表示後面的文字，只有在 **縮行改變** 或 **空行** 時，YAML 才會視為新的資料。

```YAML
node: >
   This is a book.
   it is a pen.
      I want to supermark.
```

取回的 node 資料結果，如下

```
This is a book. it is a pen.
 I want to supermark.
```

### 04. 重複的節點資料

有時，我們希望某個節點被 **重複引用**。這時，就可以將 **`&`**、**`*`** 兩個符號會搭配在一起使用。

`&` 錨點，也是希望被重複引用的節點位置。

```
bill-to: &id001
  given: Chris
  family: Dumars
```

`*` 要引用其他節點的位置

```
ship-to: *id001
```

在上面的範例，取出來的 `bill-to` 與 `ship-to` 的內容是相同的。

### 05. 映射的複合鍵

前面使用 mapping 時，通常都是單一鍵。若需要以多個 Scalars 組成一個 key ，就要用 `?` 來組成 mapping key。

```
? - Detroit Tigers
  - Chicago cubs
:
  - 2001-07-23
```

出處 [14. 延伸補充 - YAML - iT 邦幫忙::一起幫忙解決難題，拯救 IT 人的一天 (ithome.com.tw)](https://ithelp.ithome.com.tw/articles/10206454)

# Kafka

# 相關連結

##### 【尚硅谷】2022版Kafka3.x教程（从入门到调优，深入全面）

- [https://www.youtube.com/playlist?list=PLmOn9nNkQxJHTVxt3wxWXyheQPLlh-9T6](https://www.youtube.com/playlist?list=PLmOn9nNkQxJHTVxt3wxWXyheQPLlh-9T6)
- [https://s8jl-my.sharepoint.com/:f:/g/personal/atguigu\_s8jl\_onmicrosoft\_com/EoZ8qlFvkKpHr8Sq1YITz\_ABBxlt2ZHjD1ldArUVOqf4Qw?e=P2UAe7](https://www.youtube.com/redirect?event=comments&redir_token=QUFFLUhqbWxCX210WFdxYmN4LUFzV21LMWpUTnJmb3pJUXxBQ3Jtc0ttalBEMTNQaUpEcHN3YXhjMWxpM0dfc2t3UXZlcWZXby1zbTBHMllma1dDZEZlWGo4d0VwVW54ZGlnVm5qdGhZX2xjNlJLb3NwUnFHUUZ5UDA1QkRjOXhuOFVlV2ZrRG1CamtfRG91N0JodWNTY2drcw&q=https%3A%2F%2Fs8jl-my.sharepoint.com%2F%3Af%3A%2Fg%2Fpersonal%2Fatguigu_s8jl_onmicrosoft_com%2FEoZ8qlFvkKpHr8Sq1YITz_ABBxlt2ZHjD1ldArUVOqf4Qw%3Fe%3DP2UAe7&stzid=UgxYU-Q3t1uApRb9AYZ4AaABAg)

##### 【尚硅谷】SpringBoot3零基础教程

笔记：[https://www.yuque.com/leifengyang/springboot3](https://www.yuque.com/leifengyang/springboot3)  
代码：[https://gitee.com/leifengyang/spring-boot-3](https://gitee.com/leifengyang/spring-boot-3)

- 076、SpringBoot3 消息服务 Kafka简介与使用  
    [https://www.youtube.com/watch?v=hgHFZcSogow&amp;list=PLmOn9nNkQxJEeIH75s5pdTUnCo9-xOc7c&amp;index=76](https://www.youtube.com/watch?v=hgHFZcSogow&list=PLmOn9nNkQxJEeIH75s5pdTUnCo9-xOc7c&index=76)
- 077、SpringBoot3 消息服务 使用KafkaTemplate发送消息  
    [https://www.youtube.com/watch?v=YG-ezDIxFEI&amp;list=PLmOn9nNkQxJEeIH75s5pdTUnCo9-xOc7c&amp;index=77](https://www.youtube.com/watch?v=YG-ezDIxFEI&list=PLmOn9nNkQxJEeIH75s5pdTUnCo9-xOc7c&index=77)
- 078、SpringBoot3 消息服务 使用KafkaListener监听消息  
    [https://www.youtube.com/watch?v=Haxp9th26y0&amp;list=PLmOn9nNkQxJEeIH75s5pdTUnCo9-xOc7c&amp;index=78](https://www.youtube.com/watch?v=Haxp9th26y0&list=PLmOn9nNkQxJEeIH75s5pdTUnCo9-xOc7c&index=78)
- 079、SpringBoot3 消息服务 小结  
    [https://www.youtube.com/watch?v=njIPA3VBbQ4&amp;list=PLmOn9nNkQxJEeIH75s5pdTUnCo9-xOc7c&amp;index=79](https://www.youtube.com/watch?v=njIPA3VBbQ4&list=PLmOn9nNkQxJEeIH75s5pdTUnCo9-xOc7c&index=79)

#####  

##### Confluent platform

- [https://docs.confluent.io/platform/current/platform-quickstart.html](https://docs.confluent.io/platform/current/platform-quickstart.html)
- [https://github.com/confluentinc/cp-all-in-one/blob/7.5.1-post/cp-all-in-one-community/docker-compose.yml](https://github.com/confluentinc/cp-all-in-one/blob/7.5.1-post/cp-all-in-one-community/docker-compose.yml)

# 名詞解釋

#### Kafka Producer &amp; Kafka Consumer

Kafka 是一個分散式消息系統，它允許應用程序之間進行高效、可擴展的異步通信。在 Kafka 中，有兩個主要的元件，分別是生產者（Producer）和消費者（Consumer）。

1\. Kafka 生產者（Producer）：  
\- 生產者是負責將數據消息發送到 Kafka 集群的應用程序或服務。  
\- 生產者將消息發布到 Kafka 主題（Topic），主題是消息的分類或主題。  
\- 生產者可以將消息發布到一個或多個主題，並可以指定消息的密鑰（Key）和分區（Partition）。  
\- 生產者將消息寫入 Kafka 集群後，它們不再關心消息的後續處理。生產者可以持續發送消息，而不必等待消費者處理。

2\. Kafka 消費者（Consumer）：  
\- 消費者是負責從 Kafka 主題中讀取消息的應用程序或服務。  
\- 消費者可以訂閱一個或多個主題，以接收相關的消息。  
\- 消費者通常以消費者組（Consumer Group）的形式運行，每個消費者組可以有多個消費者實例，這些實例共同消費主題上的消息。  
\- Kafka 確保每條消息只被消費者組中的一個消費者實例處理，以實現消息的平衡分發。  
\- 消費者從指定的分區讀取消息，並可以在讀取消息後進行處理，例如數據處理、存儲或其他操作。

總結來說，Kafka 生產者用於將消息寫入 Kafka 主題，而 Kafka 消費者用於從主題中讀取消息，進行處理或儲存等操作。消費者通常以消費者組運行，以實現消息處理的分佈式和冗餘性，以確保高可用性和可擴展性。這使 Kafka 成為一個非常強大的消息系統，適用於大規模、高流量的數據流處理應用。

---

####  

#### Kafka Broker

Kafka Broker 是 Apache Kafka 集群中的核心元件之一，它是負責接收、存儲和分發消息的伺服器。以下是有關 Kafka Broker 的簡介：

1\. 伺服器角色：Kafka Broker 是 Kafka 集群中運行 Kafka 伺服器軟體的實例。一個 Kafka 集群通常包含多個 Broker，它們共同協作以實現消息的分發和冗餘。

2\. 消息存儲：Kafka Broker 負責存儲發送到 Kafka 集群的消息。這些消息存儲在 Broker 上的主題（Topics）中，每個主題都可以有多個分區（Partitions）。消息被持久性地保存，並根據配置的保留策略保留一段時間。

3\. 分發消息：Kafka Broker 通過提供發送消息的接口來允許 Kafka 生產者將消息發佈到特定主題。同時，它也提供訂閱消息的接口，以供 Kafka 消費者讀取和處理消息。Broker 負責確保消息從生產者到消費者之間的有效分發。

4\. 高可用性：Kafka 集群通常包含多個 Broker，這樣即使其中一個 Broker 發生故障，系統仍然可用。Kafka 通過分區（Partitions）的複製和領袖（Leader）和追隨者（Follower）的概念實現高可用性，確保即使在 Broker 失故障情況下，數據仍然可用。

5\. 資料保留：Kafka Broker 配置了消息的保留策略，該策略確定消息存儲的時間長度。這允許根據需求保存數據，以滿足不同應用程序和法規的要求。

總之，Kafka Broker 是 Kafka 集群的核心組件，負責消息的接收、存儲和分發。它確保了高可用性、可擴展性和持久性，使 Kafka 成為一個流行的選擇，用於處理實時數據流、日誌記錄、事件驅動架構等各種應用。

---

#### ZooKeeper

Apache ZooKeeper（通常簡稱ZooKeeper）是一個開源的分散式協調服務，用於協助分散式應用程序協調和管理數據。ZooKeeper的主要目標是提供一個高可用性的環境，用於解決分散式系統中的一些共同問題，例如配置管理、分布式鎖、協調和分發信息等。以下是有關ZooKeeper的關鍵概念和功能的簡要說明：

1. **分布式系統協調**：ZooKeeper旨在為分散式應用程序提供一個共享的協調和控制基礎設施。它可以幫助不同部分的應用程序在分散式環境中協同工作。
2. **分布式配置管理**：ZooKeeper可以用來存儲和管理分散式系統的配置信息，例如主機名、端口、連接字符串等。這使得系統的配置變更變得容易，並且能夠在運行時進行動態更新。
3. **分布式鎖**：ZooKeeper提供了一種機制來實現分布式鎖，以協調多個進程之間的操作順序，防止競爭條件，確保操作的原子性。
4. **分布式協議**：ZooKeeper可以用於實現分布式協議，確保多個節點之間的相對順序，以協調事件的發生。
5. **數據一致性**：ZooKeeper保證了分散式環境中的數據一致性，這對於需要強一致性的應用程序非常重要。
6. **高可用性**：ZooKeeper自身的架構具有高可用性，並且能夠容忍部分節點的故障。它使用選主（Leader）和追隨者（Follower）的模型，確保即使在部分節點故障的情況下，系統仍能正常運行。
7. **輕量和快速**：ZooKeeper是一個輕量級的服務，並且可以快速執行。它設計用於高吞吐量和低延遲的操作。
8. **開發者友好**：ZooKeeper提供了多種編程語言的客戶端庫，使開發者可以方便地與ZooKeeper集成。

ZooKeeper通常用於協調分散式系統的操作，例如Apache Kafka、Apache HBase、分布式數據庫等。它提供了一個可靠的基礎設施，以解決分散式系統中的一些關鍵問題，並確保各種應用程序能夠協同工作並達到高可用性和一致性。

---

#### Kafka Connect

Kafka Connect是用來連接Kafka和外部資料系統的工具，它提供了資料匯入和匯出的能力。以下是Kafka Connect的主要功能：

1. **資料匯入：** Kafka Connect 可用於從外部資料來源（如資料庫、日誌檔案、訊息佇列等）將資料匯入到 Kafka 主題。它提供了各種連接器，方便輕鬆將不同資料來源的資料傳輸到 Kafka。
2. **資料匯出：** Kafka Connect 也可用於將 Kafka 主題中的資料匯出至外部資料系統。這使得您可以將 Kafka 作為資料整合的中間層，將資料傳遞到多個目的地。
3. **連接器管理：** Kafka Connect提供了可設定的連接器，用於定義資料來源和目標之間的資料流。這些連接器可以配置、管理和監視，以確保資料的可靠傳輸。
4. **多元化和可擴展性：** Kafka Connect是多元化的，可以在多個工作節點上運行，以實現高吞吐量和可擴展性。

在Kafka結構中，SchemaRegistry和KafkaConnect通常一起使用，以確保資料的有效傳輸系統和一致性。KafkaConnect用於連接不同的資料來源和目標，而SchemaRegistry用於管理資料模式，以確保資料的一致這兩個元件協同工作，有助於建立強大的資料流處理應用程式。

---

#### Schema Registry

SchemaRegistry是Kafka生態系中的關鍵元件，用於管理Avro、JSONSchema等訊息化序列化和反序列化的模式。以下是SchemaRegistry的主要功能：

1. **模式管理：** SchemaRegistry用於儲存和管理不同訊息主題的資料模式。它允許Kafka生產者和消費者使用統一的資料模式，確​​保資料的一致性和可互通性。
2. **模式註冊：**生產者在將訊息傳送到Kafka時，將訊息的資料模式註冊到Schema註冊表。這使得消費者可以取得訊息的模式，以便正確解析和反序列化資料。
3. **模式演進：**當資料模式需要變更時，模式註冊表允許進行模式演進，以確保新的和舊的訊息都能夠被處理。這有助於系統的升級和維護。
4. **資料驗證：** SchemaRegistry可以驗證傳送到Kafka主題的訊息是否與註冊的資料模式相容，從而保證資料的一致性和一致性。

---

# Docker建立Kafka cluster

[![image-1697801704673.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-10/scaled-1680-/r8KN5mSLRC0AZljB-image-1697801704673.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-10/r8KN5mSLRC0AZljB-image-1697801704673.png)

docker-compose.yml

10.20.30.40 -&gt; 改成自己的ip

```YAML
---
version: '2'
services:
  kafka-ui:
    container_name: kafka-ui
    image: provectuslabs/kafka-ui:latest
    ports:
      - 8081:8080
    depends_on:
      - kafka0
      - kafka1
      - kafka2
      - schema-registry0
      - kafka-connect0
    environment:
      KAFKA_CLUSTERS_0_NAME: local
      KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka0:29092,kafka1:29092,kafka2:29092
      KAFKA_CLUSTERS_0_METRICS_PORT: 9997
      KAFKA_CLUSTERS_0_SCHEMAREGISTRY: http://schema-registry0:8085
      KAFKA_CLUSTERS_0_KAFKACONNECT_0_NAME: first
      KAFKA_CLUSTERS_0_KAFKACONNECT_0_ADDRESS: http://kafka-connect0:8083

  kafka0:
    image: confluentinc/cp-kafka:7.2.1
    hostname: kafka0
    container_name: kafka0
    ports:
      - 9092:9092
      - 9997:9997
    environment:
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka0:29092,PLAINTEXT_HOST://10.20.30.40:9092
      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
      KAFKA_PROCESS_ROLES: 'broker,controller'
      KAFKA_CLUSTER_ID:
      KAFKA_NODE_ID: 1
      KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka0:29093,2@kafka1:29093,3@kafka2:29093'
      KAFKA_LISTENERS: 'PLAINTEXT://kafka0:29092,CONTROLLER://kafka0:29093,PLAINTEXT_HOST://0.0.0.0:9092'
      KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
      KAFKA_LOG_DIRS: '/tmp/kraft-combined-logs'
      KAFKA_JMX_PORT: 9997
      KAFKA_JMX_OPTS: -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=kafka0 -Dcom.sun.management.jmxremote.rmi.port=9997
    volumes:
      - ./scripts/update_run_cluster.sh:/tmp/update_run.sh
      - ./scripts/clusterID:/tmp/clusterID
      - ./data/kafka0/kraft-combined-logs:/tmp/kraft-combined-logs
    command: "bash -c 'if [ ! -f /tmp/update_run.sh ]; then echo \"ERROR: Did you forget the update_run.sh file that came with this docker-compose.yml file?\" && exit 1 ; else /tmp/update_run.sh && /etc/confluent/docker/run ; fi'"

  kafka1:
    image: confluentinc/cp-kafka:7.2.1
    hostname: kafka1
    container_name: kafka1
    ports:
      - 9093:9092
    environment:
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka1:29092,PLAINTEXT_HOST://10.20.30.40:9093
      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
      KAFKA_PROCESS_ROLES: 'broker,controller'
      KAFKA_NODE_ID: 2
      KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka0:29093,2@kafka1:29093,3@kafka2:29093'
      KAFKA_LISTENERS: 'PLAINTEXT://kafka1:29092,CONTROLLER://kafka1:29093,PLAINTEXT_HOST://0.0.0.0:9092'
      KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
      KAFKA_LOG_DIRS: '/tmp/kraft-combined-logs'
      KAFKA_JMX_PORT: 9997
      KAFKA_JMX_OPTS: -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=kafka1 -Dcom.sun.management.jmxremote.rmi.port=9997
    volumes:
      - ./scripts/update_run_cluster.sh:/tmp/update_run.sh
      - ./scripts/clusterID:/tmp/clusterID
      - ./data/kafka1/kraft-combined-logs:/tmp/kraft-combined-logs
    command: "bash -c 'if [ ! -f /tmp/update_run.sh ]; then echo \"ERROR: Did you forget the update_run.sh file that came with this docker-compose.yml file?\" && exit 1 ; else /tmp/update_run.sh && /etc/confluent/docker/run ; fi'"

  kafka2:
    image: confluentinc/cp-kafka:7.2.1
    hostname: kafka2
    container_name: kafka2
    ports:
      - 9094:9092
    environment:
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka2:29092,PLAINTEXT_HOST://10.20.30.40:9094
      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
      KAFKA_PROCESS_ROLES: 'broker,controller'
      KAFKA_NODE_ID: 3
      KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka0:29093,2@kafka1:29093,3@kafka2:29093'
      KAFKA_LISTENERS: 'PLAINTEXT://kafka2:29092,CONTROLLER://kafka2:29093,PLAINTEXT_HOST://0.0.0.0:9092'
      KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
      KAFKA_LOG_DIRS: '/tmp/kraft-combined-logs'
      KAFKA_JMX_PORT: 9997
      KAFKA_JMX_OPTS: -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=kafka1 -Dcom.sun.management.jmxremote.rmi.port=9997
    volumes:
      - ./scripts/update_run_cluster.sh:/tmp/update_run.sh
      - ./scripts/clusterID:/tmp/clusterID
      - ./data/kafka2/kraft-combined-logs:/tmp/kraft-combined-logs
    command: "bash -c 'if [ ! -f /tmp/update_run.sh ]; then echo \"ERROR: Did you forget the update_run.sh file that came with this docker-compose.yml file?\" && exit 1 ; else /tmp/update_run.sh && /etc/confluent/docker/run ; fi'"


  schema-registry0:
    image: confluentinc/cp-schema-registry:7.2.1
    ports:
      - 8085:8085
    depends_on:
      - kafka0
    environment:
      SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: PLAINTEXT://kafka0:29092
      SCHEMA_REGISTRY_KAFKASTORE_SECURITY_PROTOCOL: PLAINTEXT
      SCHEMA_REGISTRY_HOST_NAME: schema-registry0
      SCHEMA_REGISTRY_LISTENERS: http://schema-registry0:8085

      SCHEMA_REGISTRY_SCHEMA_REGISTRY_INTER_INSTANCE_PROTOCOL: "http"
      SCHEMA_REGISTRY_LOG4J_ROOT_LOGLEVEL: INFO
      SCHEMA_REGISTRY_KAFKASTORE_TOPIC: _schemas

  kafka-connect0:
    image: confluentinc/cp-kafka-connect:7.2.1
    ports:
      - 8083:8083
    depends_on:
      - kafka0
      - schema-registry0
    environment:
      CONNECT_BOOTSTRAP_SERVERS: kafka0:29092
      CONNECT_GROUP_ID: compose-connect-group
      CONNECT_CONFIG_STORAGE_TOPIC: _connect_configs
      CONNECT_CONFIG_STORAGE_REPLICATION_FACTOR: 1
      CONNECT_OFFSET_STORAGE_TOPIC: _connect_offset
      CONNECT_OFFSET_STORAGE_REPLICATION_FACTOR: 1
      CONNECT_STATUS_STORAGE_TOPIC: _connect_status
      CONNECT_STATUS_STORAGE_REPLICATION_FACTOR: 1
      CONNECT_KEY_CONVERTER: org.apache.kafka.connect.storage.StringConverter
      CONNECT_KEY_CONVERTER_SCHEMA_REGISTRY_URL: http://schema-registry0:8085
      CONNECT_VALUE_CONVERTER: org.apache.kafka.connect.storage.StringConverter
      CONNECT_VALUE_CONVERTER_SCHEMA_REGISTRY_URL: http://schema-registry0:8085
      CONNECT_INTERNAL_KEY_CONVERTER: org.apache.kafka.connect.json.JsonConverter
      CONNECT_INTERNAL_VALUE_CONVERTER: org.apache.kafka.connect.json.JsonConverter
      CONNECT_REST_ADVERTISED_HOST_NAME: kafka-connect0
      CONNECT_PLUGIN_PATH: "/usr/share/java,/usr/share/confluent-hub-components,/usr/share/filestream-connectors,/tmp/kfk"
    volumes:
      - ./data/kfk:/tmp/kfk:ro
      - ./data/kfk/test.txt:/tmp/kfk/test.txt

  # kafka-init-topics:
  #   image: confluentinc/cp-kafka:7.2.1
  #   volumes:
  #     - ./data/message.json:/data/message.json
  #   depends_on:
  #     - kafka0
  #   command: "bash -c 'echo Waiting for Kafka to be ready... && \
  #             cub kafka-ready -b kafka0:29092 1 30 && \
  #             kafka-topics --create --topic second.users --partitions 3 --replication-factor 1 --if-not-exists --bootstrap-server kafka0:29092 && \
  #             kafka-topics --create --topic second.messages --partitions 2 --replication-factor 1 --if-not-exists --bootstrap-server kafka0:29092 && \
  #             kafka-topics --create --topic first.messages --partitions 2 --replication-factor 1 --if-not-exists --bootstrap-server kafka0:29092 && \
  #             kafka-console-producer --bootstrap-server kafka0:29092 --topic second.users < /data/message.json'"


# https://docs.kafka-ui.provectus.io/configuration/complex-configuration-examples/kraft-mode-+-multiple-brokers
```

data/message.json

```
{}
```

data/proxy.conf

```Nginx
server {
    listen       80;
    server_name  localhost;

    location /kafka-ui {
#        rewrite /kafka-ui/(.*) /$1  break;
        proxy_pass   http://kafka-ui:8080;
    }
}

```

scripts/clusterID

```
zlFiTJelTOuhnklFwLWixw
```

scripts/create\_cluster\_id.sh

```shell
kafka-storage random-uuid > /workspace/kafka-ui/documentation/compose/clusterID
```

scripts/create\_update\_run\_cluster.sh

```shell
# This script is required to run kafka cluster (without zookeeper)
#!/bin/sh

# Docker workaround: Remove check for KAFKA_ZOOKEEPER_CONNECT parameter
sed -i '/KAFKA_ZOOKEEPER_CONNECT/d' /etc/confluent/docker/configure

# Docker workaround: Ignore cub zk-ready
sed -i 's/cub zk-ready/echo ignore zk-ready/' /etc/confluent/docker/ensure

# KRaft required step: Format the storage directory with a new cluster ID
echo "kafka-storage format --ignore-formatted -t $(cat /tmp/clusterID) -c /etc/kafka/kafka.properties" >> /etc/confluent/docker/ensure
```

scripts/update\_run.sh

```shell
# This script is required to run kafka cluster (without zookeeper)
#!/bin/sh

# Docker workaround: Remove check for KAFKA_ZOOKEEPER_CONNECT parameter
sed -i '/KAFKA_ZOOKEEPER_CONNECT/d' /etc/confluent/docker/configure

# Docker workaround: Ignore cub zk-ready
sed -i 's/cub zk-ready/echo ignore zk-ready/' /etc/confluent/docker/ensure

# KRaft required step: Format the storage directory with a new cluster ID
echo "kafka-storage format --ignore-formatted -t $(kafka-storage random-uuid) -c /etc/kafka/kafka.properties" >> /etc/confluent/docker/ensure
```

# Kafka 架構

[![image-1697882384063.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-10/scaled-1680-/vCoX9tSQGKA3373X-image-1697882384063.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-10/vCoX9tSQGKA3373X-image-1697882384063.png)

[![image-1697882567175.png](https://bookstack.treemanou.com/uploads/images/gallery/2023-10/scaled-1680-/tUz9hEgcE5wati7b-image-1697882567175.png)](https://bookstack.treemanou.com/uploads/images/gallery/2023-10/tUz9hEgcE5wati7b-image-1697882567175.png)

# Kafka 學習筆記

<div id="bkmrk-%E4%B8%80%E3%80%81%E6%A6%82%E8%BF%B0">一、概述</div><div id="bkmrk-1%E3%80%81%E5%AE%9A%E4%B9%89">1、定义</div><div id="bkmrk-1%EF%BC%89%E4%BC%A0%E7%BB%9F%E5%AE%9A%E4%B9%89">1）传统定义</div><div id="bkmrk-%E5%88%86%E5%B8%83%E5%BC%8F%C2%A0-%E5%8F%91%E5%B8%83%E8%AE%A2%E9%98%85%C2%A0-%C2%A0%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97">分布式 发布订阅 消息队列</div><div id="bkmrk-%E5%8F%91%E5%B8%83%E8%AE%A2%E9%98%85%EF%BC%9A%E5%88%86%E4%B8%BA%E5%A4%9A%E7%A7%8D%E7%B1%BB%E5%9E%8B-%E8%AE%A2%E9%98%85%E8%80%85%E6%A0%B9%E6%8D%AE%E9%9C%80%E6%B1%82-">发布订阅：分为多种类型 订阅者根据需求 选择性订阅</div><div id="bkmrk-2%EF%BC%89%E6%9C%80%E6%96%B0%E5%AE%9A%E4%B9%89">2）最新定义</div><div id="bkmrk-%E6%B5%81%E5%B9%B3%E5%8F%B0%EF%BC%88%E5%AD%98%E5%82%A8%E3%80%81%E8%AE%A1%E7%AE%97%EF%BC%89">流平台（存储、计算）</div><div id="bkmrk-2%E3%80%81%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF">2、消息队列应用场景</div><div id="bkmrk-1%EF%BC%89%E7%BC%93%E5%AD%98%E6%B6%88%E5%B3%B0%C2%A0">1）缓存消峰 </div><div id="bkmrk-2%EF%BC%89%E8%A7%A3%E8%80%A6">2）解耦</div><div id="bkmrk-3%EF%BC%89%E5%BC%82%E6%AD%A5%E9%80%9A%E4%BF%A1">3）异步通信</div><div id="bkmrk-3%E3%80%81%E4%B8%A4%E7%A7%8D%E6%A8%A1%E5%BC%8F">3、两种模式</div><div id="bkmrk-1%EF%BC%89%E7%82%B9%E5%AF%B9%E7%82%B9">1）点对点</div><div id="bkmrk-%EF%BC%881%EF%BC%89%E4%B8%80%E4%B8%AA%E7%94%9F%E4%BA%A7%E8%80%85-%E4%B8%80%E4%B8%AA%E6%B6%88%E8%B4%B9%E8%80%85-%E4%B8%80%E4%B8%AAtop">（1）一个生产者 一个消费者 一个topic 会删除数据 不多</div><div id="bkmrk-"></div><div id="bkmrk-2%EF%BC%89%E5%8F%91%E5%B8%83%E8%AE%A2%E9%98%85">2）发布订阅</div><div id="bkmrk-%EF%BC%881%EF%BC%89%E5%A4%9A%E4%B8%AA%E7%94%9F%E4%BA%A7%E8%80%85%C2%A0-%E6%B6%88%E8%B4%B9%E8%80%85%E5%A4%9A%E4%B8%AA%C2%A0-%E8%80%8C%E4%B8%94%E7%9B%B8">（1）多个生产者 消费者多个 而且相互独立 多个topic 不会删除数据</div><div id="bkmrk-4%E3%80%81%E6%9E%B6%E6%9E%84">4、架构</div><div id="bkmrk-1%EF%BC%89%E7%94%9F%E4%BA%A7%E8%80%85">1）生产者</div><div id="bkmrk-100t%E6%95%B0%E6%8D%AE">100T数据</div><div id="bkmrk--0"></div><div id="bkmrk-2%EF%BC%89broker%C2%A0">2）broker </div><div id="bkmrk-%EF%BC%881%EF%BC%89broker%C2%A0-%E6%9C%8D%E5%8A%A1%E5%99%A8-hadoo">（1）broker 服务器 hadoop102 103 104</div><div id="bkmrk-%EF%BC%882%EF%BC%89topic-%E4%B8%BB%E9%A2%98%C2%A0-%E5%AF%B9%E6%95%B0%E6%8D%AE%E5%88%86%E7%B1%BB">（2）topic 主题 对数据分类</div><div id="bkmrk-%EF%BC%883%EF%BC%89%E5%88%86%E5%8C%BA">（3）分区</div><div id="bkmrk-%EF%BC%884%EF%BC%89%E5%8F%AF%E9%9D%A0%E6%80%A7%C2%A0-%E5%89%AF%E6%9C%AC">（4）可靠性 副本</div><div id="bkmrk-%EF%BC%885%EF%BC%89leader%C2%A0-follower%C2%A0">（5）leader follower </div><div id="bkmrk-%EF%BC%886%EF%BC%89%E7%94%9F%E4%BA%A7%E8%80%85%E5%92%8C%E6%B6%88%E8%B4%B9%E8%80%85-%E5%8F%AA%E9%92%88%E5%AF%B9leader">（6）生产者和消费者 只针对leader操作</div><div id="bkmrk--1"></div><div id="bkmrk--2"></div><div id="bkmrk-3%EF%BC%89%E6%B6%88%E8%B4%B9%E8%80%85">3）消费者</div><div id="bkmrk-%EF%BC%881%EF%BC%89%E6%B6%88%E8%B4%B9%E8%80%85%E5%92%8C%E6%B6%88%E8%B4%B9%E8%80%85%E7%9B%B8%E4%BA%92%E7%8B%AC%E7%AB%8B">（1）消费者和消费者相互独立</div><div id="bkmrk-%EF%BC%882%EF%BC%89%E6%B6%88%E8%B4%B9%E8%80%85%E7%BB%84-%EF%BC%88%E6%9F%90%E4%B8%AA%E5%88%86%E5%8C%BA-%E5%8F%AA%E8%83%BD%E7%94%B1%E4%B8%80%E4%B8%AA%E6%B6%88">（2）消费者组 （某个分区 只能由一个消费者消费）</div><div id="bkmrk--3"></div><div id="bkmrk--4"></div><div id="bkmrk-4%EF%BC%89zookeeper">4）zookeeper</div><div id="bkmrk-%EF%BC%881%EF%BC%89broker.ids-0-1-2%C2%A0">（1）broker.ids 0 1 2 </div><div id="bkmrk-%EF%BC%882%EF%BC%89leader%C2%A0">（2）leader </div><div id="bkmrk-%E4%BA%8C%E3%80%81%E5%85%A5%E9%97%A8">二、入门</div><div id="bkmrk-1%E3%80%81%E5%AE%89%E8%A3%85">1、安装</div><div id="bkmrk-1%EF%BC%89broker.id%C2%A0-%E5%BF%85%E9%A1%BB%E5%85%A8%E5%B1%80%E5%94%AF%E4%B8%80">1）broker.id 必须全局唯一</div><div id="bkmrk-2%EF%BC%89broker.id%E3%80%81log.dirs">2）broker.id、log.dirs zk/kafka</div><div id="bkmrk-3%EF%BC%89%E5%90%AF%E5%8A%A8%E5%81%9C%E6%AD%A2%C2%A0-%E5%85%88%E5%81%9C%E6%AD%A2kafka%C2%A0-%E5%86%8D%E5%81%9C">3）启动停止 先停止kafka 再停zk</div><div id="bkmrk-4%EF%BC%89%E8%84%9A%E6%9C%AC">4）脚本</div><div id="bkmrk-%23%21%2Fbin%2Fbash">\#!/bin/bash</div><div id="bkmrk--5"></div><div id="bkmrk-case-%241-in">case $1 in</div><div id="bkmrk-%22start%22%29">"start")</div><div id="bkmrk-for-i-in-hadoop102-h">for i in hadoop102 hadoop103 hadoop104</div><div id="bkmrk-do">do</div><div id="bkmrk-ssh-%24i-%22%E7%BB%9D%E5%AF%B9%E8%B7%AF%E5%BE%84%22">ssh $i "绝对路径"</div><div id="bkmrk-done">done</div><div id="bkmrk-%3B%3B">;;</div><div id="bkmrk-%22stop%22%29">"stop")</div><div id="bkmrk--6"></div><div id="bkmrk-%3B%3B-0">;;</div><div id="bkmrk-esac">esac</div><div id="bkmrk-2%E3%80%81%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4%E8%A1%8C">2、常用命令行</div><div id="bkmrk-1%EF%BC%89%E4%B8%BB%E9%A2%98-kafka-topic.sh%C2%A0">1）主题 kafka-topic.sh </div><div id="bkmrk-%EF%BC%881%EF%BC%89--bootstrap-serve">（1）--bootstrap-server hadoop102:9092,hadoop103:9092</div><div id="bkmrk-%EF%BC%882%EF%BC%89--topic-first">（2）--topic first</div><div id="bkmrk-%EF%BC%883%EF%BC%89--create%C2%A0">（3）--create </div><div id="bkmrk-%EF%BC%884%EF%BC%89--delete">（4）--delete</div><div id="bkmrk-%EF%BC%885%EF%BC%89--alter">（5）--alter</div><div id="bkmrk-%EF%BC%886%EF%BC%89--list">（6）--list</div><div id="bkmrk-%EF%BC%887%EF%BC%89--describe">（7）--describe</div><div id="bkmrk-%EF%BC%888%EF%BC%89--partitions">（8）--partitions</div><div id="bkmrk-%EF%BC%889%EF%BC%89--replication-fac">（9）--replication-factor </div><div id="bkmrk--7"></div><div id="bkmrk-2%EF%BC%89%E7%94%9F%E4%BA%A7%E8%80%85-kafka-console-">2）生产者 kafka-console-producer.sh </div><div id="bkmrk-%EF%BC%881%EF%BC%89--bootstrap-serve-0">（1）--bootstrap-server hadoop102:9092,hadoop103:9092</div><div id="bkmrk-%EF%BC%882%EF%BC%89--topic-first-0">（2）--topic first</div><div id="bkmrk--8"></div><div id="bkmrk--9"></div><div id="bkmrk-3%EF%BC%89%E6%B6%88%E8%B4%B9%E8%80%85-kafka-console-">3）消费者 kafka-console-consumer.sh </div><div id="bkmrk-%EF%BC%881%EF%BC%89--bootstrap-serve-1">（1）--bootstrap-server hadoop102:9092,hadoop103:9092</div><div id="bkmrk-%EF%BC%882%EF%BC%89--topic-first-1">（2）--topic first</div><div id="bkmrk--10"></div><div id="bkmrk-%E4%B8%89%E3%80%81%E7%94%9F%E4%BA%A7%E8%80%85%C2%A0">三、生产者 </div><div id="bkmrk-1%E3%80%81%E5%8E%9F%E7%90%86">1、原理</div><div id="bkmrk--11"></div><div id="bkmrk-2%E3%80%81%E5%BC%82%E6%AD%A5%E5%8F%91%E9%80%81api">2、异步发送API</div><div id="bkmrk-0%EF%BC%89%E9%85%8D%E7%BD%AE">0）配置</div><div id="bkmrk-%EF%BC%881%EF%BC%89%E8%BF%9E%E6%8E%A5%C2%A0-bootstrap-ser">（1）连接 bootstrap-server </div><div id="bkmrk-%EF%BC%882%EF%BC%89key-value%E5%BA%8F%E5%88%97%E5%8C%96">（2）key value序列化</div><div id="bkmrk--12"></div><div id="bkmrk-1%EF%BC%89%E5%88%9B%E5%BB%BA%E7%94%9F%E4%BA%A7%E8%80%85">1）创建生产者</div><div id="bkmrk-kafkaproducer%3Cstring">KafkaProducer&lt;String,String&gt;()</div><div id="bkmrk--13"></div><div id="bkmrk-2%EF%BC%89%E5%8F%91%E9%80%81%E6%95%B0%E6%8D%AE">2）发送数据</div><div id="bkmrk-send%28%29-send%28%2Cnew-cal">send() send(,new Callback)</div><div id="bkmrk--14"></div><div id="bkmrk-3%EF%BC%89%E5%85%B3%E9%97%AD%E8%B5%84%E6%BA%90">3）关闭资源</div><div id="bkmrk-3%E3%80%81%E5%90%8C%E6%AD%A5%E5%8F%91%E9%80%81">3、同步发送</div><div id="bkmrk-%E3%80%82%E3%80%82%E3%80%82">。。。</div><div id="bkmrk--15"></div><div id="bkmrk-send%28%29-send%28%2Cnew-cal-0">send() send(,new Callback).get()</div><div id="bkmrk-%E3%80%82%E3%80%82%E3%80%82-0">。。。</div><div id="bkmrk-4%E3%80%81%E5%88%86%E5%8C%BA">4、分区</div><div id="bkmrk-1%EF%BC%89%E5%A5%BD%E5%A4%84">1）好处</div><div id="bkmrk-%E5%AD%98%E5%82%A8">存储</div><div id="bkmrk--16"></div><div id="bkmrk-%E8%AE%A1%E7%AE%97">计算</div><div id="bkmrk-2%EF%BC%89%E9%BB%98%E8%AE%A4%E5%88%86%E5%8C%BA%E8%A7%84%E5%88%99">2）默认分区规则</div><div id="bkmrk-%EF%BC%881%EF%BC%89%E6%8C%87%E5%AE%9A%E5%88%86%E5%8C%BA-%E6%8C%89%E5%88%86%E5%8C%BA%E8%B5%B0">（1）指定分区 按分区走</div><div id="bkmrk-%EF%BC%882%EF%BC%89key%C2%A0-key%E7%9A%84hashcode">（2）key key的hashcode值%分区数</div><div id="bkmrk-%EF%BC%883%EF%BC%89%E6%B2%A1%E6%9C%89%E6%8C%87%E5%AE%9Akey%C2%A0-%E6%B2%A1%E6%9C%89%E6%8C%87%E5%AE%9A%E5%88%86%E5%8C%BA%C2%A0-">（3）没有指定key 没有指定分区 粘性</div><div id="bkmrk-%E7%AC%AC%E4%B8%80%E9%9A%8F%E6%9C%BA">第一随机</div><div id="bkmrk-3%EF%BC%89%E8%87%AA%E5%AE%9A%E4%B9%89%E5%88%86%E5%8C%BA">3）自定义分区</div><div id="bkmrk-%E5%AE%9A%E4%B9%89%E7%B1%BB-%E5%AE%9E%E7%8E%B0partitioner%E6%8E%A5%E5%8F%A3%C2%A0">定义类 实现partitioner接口 </div><div id="bkmrk-5%E3%80%81%E5%90%9E%E5%90%90%E9%87%8F%E6%8F%90%E9%AB%98">5、吞吐量提高</div><div id="bkmrk-1%EF%BC%89%E6%89%B9%E6%AC%A1%E5%A4%A7%E5%B0%8F%C2%A0-16k%C2%A0-32k">1）批次大小 16k 32k</div><div id="bkmrk-2%EF%BC%89linger.ms%C2%A0-0%C2%A0-%3D%3E-5">2）linger.ms 0 =&gt; 5-100ms</div><div id="bkmrk-3%EF%BC%89%E5%8E%8B%E7%BC%A9%C2%A0%C2%A0">3）压缩 </div><div id="bkmrk-4%EF%BC%89%E7%BC%93%E5%AD%98%E5%A4%A7%E5%B0%8F%C2%A0-32m%C2%A0-%3D%3E-64m%C2%A0">4）缓存大小 32m =&gt; 64m </div><div id="bkmrk-6%E3%80%81%E5%8F%AF%E9%9D%A0%E6%80%A7%C2%A0">6、可靠性 </div><div id="bkmrk-acks%C2%A0%C2%A0">acks </div><div id="bkmrk-0%C2%A0-%E4%B8%A2%E5%A4%B1%E6%95%B0%E6%8D%AE">0 丢失数据</div><div id="bkmrk-1%C2%A0-%C2%A0%E4%B9%9F%E5%8F%AF%E8%83%BD%E4%BC%9A%E4%B8%A2%C2%A0-%E4%BC%A0%E8%BE%93%E6%99%AE%E9%80%9A%E6%97%A5%E5%BF%97">1 也可能会丢 传输普通日志</div><div id="bkmrk--1%C2%A0-%E5%AE%8C%E5%85%A8%E5%8F%AF%E9%9D%A0%C2%A0-%2B-%E5%89%AF%E6%9C%AC%E5%A4%A7%E4%BA%8E%E7%AD%89%E4%BA%8E2%C2%A0">-1 完全可靠 + 副本大于等于2 isr &gt;=2 =&gt; 数据重复</div><div id="bkmrk-7%E3%80%81%E6%95%B0%E6%8D%AE%E9%87%8D%E5%A4%8D">7、数据重复</div><div id="bkmrk-1%EF%BC%89%E5%B9%82%E7%AD%89%E6%80%A7">1）幂等性</div><div id="bkmrk-%3Cpid%2C-%E5%88%86%E5%8C%BA%E5%8F%B7%EF%BC%8C%E5%BA%8F%E5%88%97%E5%8F%B7%3E">&lt;pid, 分区号，序列号&gt;</div><div id="bkmrk-%E9%BB%98%E8%AE%A4%E6%89%93%E5%BC%80">默认打开</div><div id="bkmrk--17"></div><div id="bkmrk-2%EF%BC%89%E4%BA%8B%E5%8A%A1">2）事务</div><div id="bkmrk-%E5%BA%95%E5%B1%82%E5%9F%BA%E4%BA%8E%E5%B9%82%E7%AD%89%E6%80%A7">底层基于幂等性</div><div id="bkmrk--18"></div><div id="bkmrk-%EF%BC%881%EF%BC%89%E5%88%9D%E5%A7%8B%E5%8C%96">（1）初始化</div><div id="bkmrk-%EF%BC%882%EF%BC%89%E5%90%AF%E5%8A%A8">（2）启动</div><div id="bkmrk-%EF%BC%883%EF%BC%89%E6%B6%88%E8%B4%B9%E8%80%85offset">（3）消费者offset</div><div id="bkmrk-%EF%BC%884%EF%BC%89%E6%8F%90%E4%BA%A4">（4）提交</div><div id="bkmrk-%EF%BC%885%EF%BC%89%E7%BB%88%E6%AD%A2">（5）终止</div><div id="bkmrk-8%E3%80%81%E6%95%B0%E6%8D%AE%E6%9C%89%E5%BA%8F">8、数据有序</div><div id="bkmrk-%E5%8D%95%E5%88%86%E5%8C%BA%E5%86%85%E6%9C%89%E5%BA%8F%EF%BC%88%E6%9C%89%E6%9D%A1%E4%BB%B6%EF%BC%89">单分区内有序（有条件）</div><div id="bkmrk-%E5%A4%9A%E5%88%86%E5%8C%BA%E6%9C%89%E5%BA%8F%E6%80%8E%E4%B9%88%E5%8A%9E%EF%BC%9F">多分区有序怎么办？</div><div id="bkmrk-9%E3%80%81%E4%B9%B1%E5%BA%8F">9、乱序</div><div id="bkmrk-1%EF%BC%89inflight%C2%A0-%3D1%C2%A0">1）inflight =1 </div><div id="bkmrk-2%EF%BC%89%E6%B2%A1%E6%9C%89%E5%B9%82%E7%AD%89%E6%80%A7-inflight%C2%A0-%3D1">2）没有幂等性 inflight =1 </div><div id="bkmrk-3%EF%BC%89%E6%9C%89%E5%B9%82%E7%AD%89%E6%80%A7">3）有幂等性</div><div id="bkmrk-%E5%9B%9B%E3%80%81broker%C2%A0">四、broker </div><div id="bkmrk-1%E3%80%81zk%E5%AD%98%E5%82%A8%E4%BA%86%E5%93%AA%E4%BA%9B%E4%BF%A1%E6%81%AF">1、zk存储了哪些信息</div><div id="bkmrk-%EF%BC%881%EF%BC%89broker.ids">（1）broker.ids</div><div id="bkmrk-%EF%BC%882%EF%BC%89leader%C2%A0-0">（2）leader </div><div id="bkmrk-%EF%BC%883%EF%BC%89%E8%BE%85%E5%8A%A9%E9%80%89%E4%B8%BE%C2%A0-controller%C2%A0">（3）辅助选举 controller </div><div id="bkmrk-2%E3%80%81%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B">2、工作流程</div><div id="bkmrk--19"></div><div id="bkmrk-3%E3%80%81%E6%9C%8D%E5%BD%B9">3、服役</div><div id="bkmrk-1%EF%BC%89%E5%87%86%E5%A4%87%E4%B8%80%E5%8F%B0%E5%B9%B2%E5%87%80%E6%9C%8D%E5%8A%A1%E5%99%A8-hadoop10">1）准备一台干净服务器 hadoop100</div><div id="bkmrk-2%EF%BC%89%E5%AF%B9%E5%93%AA%E4%B8%AA%E4%B8%BB%E9%A2%98%E6%93%8D%E4%BD%9C%C2%A0">2）对哪个主题操作 </div><div id="bkmrk-3%EF%BC%89%E5%BD%A2%E6%88%90%E8%AE%A1%E5%88%92">3）形成计划</div><div id="bkmrk-4%EF%BC%89%E6%89%A7%E8%A1%8C%E8%AE%A1%E5%88%92">4）执行计划</div><div id="bkmrk-5%EF%BC%89%E9%AA%8C%E8%AF%81%E8%AE%A1%E5%88%92">5）验证计划</div><div id="bkmrk--20"></div><div id="bkmrk-4%E3%80%81%E9%80%80%E5%BD%B9">4、退役</div><div id="bkmrk-1%EF%BC%89%E8%A6%81%E9%80%80%E5%BD%B9%E7%9A%84%E8%8A%82%E7%82%B9%E4%B8%8D%E8%AE%A9%E5%AD%98%E5%82%A8%E6%95%B0%E6%8D%AE">1）要退役的节点不让存储数据</div><div id="bkmrk-2%EF%BC%89%E9%80%80%E5%87%BA%E8%8A%82%E7%82%B9">2）退出节点</div><div id="bkmrk-5%E3%80%81%E5%89%AF%E6%9C%AC%C2%A0">5、副本 </div><div id="bkmrk-1%EF%BC%89%E5%89%AF%E6%9C%AC%E7%9A%84%E5%A5%BD%E5%A4%84%C2%A0-%C2%A0%E6%8F%90%E9%AB%98%E5%8F%AF%E9%9D%A0%E6%80%A7">1）副本的好处 提高可靠性</div><div id="bkmrk-2%EF%BC%89%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E4%B8%AD%E9%80%9A%E5%B8%B82%E4%B8%AA%C2%A0-%E9%BB%98%E8%AE%A41%E4%B8%AA">2）生产环境中通常2个 默认1个</div><div id="bkmrk-3%EF%BC%89%E6%9C%89leader-follower%C2%A0-">3）有leader follower leader </div><div id="bkmrk-4%EF%BC%89isr%C2%A0">4）isr </div><div id="bkmrk-5%EF%BC%89controller%C2%A0-isr%5B0%C2%A0">5）controller isr[0 2 3 ] 存活 ar [0 1 2 3]</div><div id="bkmrk-6%EF%BC%89leader-%E6%8C%82%E4%BA%86%C2%A0">6）Leader 挂了 </div><div id="bkmrk-7%EF%BC%89follower-%E6%8C%82%E4%BA%86%C2%A0%C2%A0">7）follower 挂了 </div><div id="bkmrk-8%EF%BC%89%E5%89%AF%E6%9C%AC%E5%88%86%E9%85%8D-%E9%BB%98%E8%AE%A4%C2%A0">8）副本分配 默认 </div><div id="bkmrk-9%EF%BC%89%E6%89%8B%E5%8A%A8%E5%89%AF%E6%9C%AC%E5%88%86%E9%85%8D%C2%A0-%E5%88%B6%E5%AE%9A%E8%AE%A1%E5%88%92%C2%A0-%E6%89%A7%E8%A1%8C%E8%AE%A1%E5%88%92">9）手动副本分配 制定计划 执行计划 验证计划 </div><div id="bkmrk-10%EF%BC%89leader-partition%E7%9A%84">10）leader partition的负载均衡 10%</div><div id="bkmrk-11%EF%BC%89%E6%89%8B%E5%8A%A8%E5%A2%9E%E5%8A%A0%E5%89%AF%E6%9C%AC%E5%9B%A0%E5%AD%90%C2%A0">11）手动增加副本因子 </div><div id="bkmrk-6%E3%80%81%E5%AD%98%E5%82%A8%E6%9C%BA%E5%88%B6%C2%A0">6、存储机制 </div><div id="bkmrk-broker%C2%A0-topic%C2%A0-parti">broker topic partition log segment 1g 稀疏索引 4kb </div><div id="bkmrk-7%E3%80%81%E5%88%A0%E9%99%A4%E6%95%B0%E6%8D%AE">7、删除数据</div><div id="bkmrk-%E9%BB%98%E8%AE%A47%E5%A4%A9%C2%A0-%C2%A03%E5%A4%A9%C2%A0-7%E5%B0%8F%E6%97%B6">默认7天 3天 7小时</div><div id="bkmrk-%E4%B8%A4%E7%A7%8D%C2%A0-%E5%88%A0%E9%99%A4%C2%A0-%E5%8E%8B%E7%BC%A9%C2%A0">两种 删除 压缩 </div><div id="bkmrk-%E5%88%A0%E9%99%A4%EF%BC%9A">删除：</div><div id="bkmrk-%E5%8E%8B%E7%BC%A9%EF%BC%9A">压缩：</div><div id="bkmrk-8%E3%80%81%E9%AB%98%E6%95%88%E8%AF%BB%E5%86%99">8、高效读写</div><div id="bkmrk-1%EF%BC%89%E9%9B%86%E7%BE%A4%C2%A0-%E9%87%87%E7%94%A8%E5%88%86%E5%8C%BA">1）集群 采用分区</div><div id="bkmrk-2%EF%BC%89%E7%A8%80%E7%96%8F%E7%B4%A2%E5%BC%95%C2%A0">2）稀疏索引 </div><div id="bkmrk-3%EF%BC%89%E9%A1%BA%E5%BA%8F%E8%AF%BB%E5%86%99%C2%A0">3）顺序读写 </div><div id="bkmrk-4%EF%BC%89%E9%9B%B6%E6%8B%B7%E8%B4%9D-%E5%92%8C%E9%A1%B5%E7%BC%93%E5%AD%98%C2%A0">4）零拷贝 和页缓存 </div><div id="bkmrk-%E4%BA%94%E3%80%81%E6%B6%88%E8%B4%B9%E8%80%85%C2%A0">五、消费者 </div><div id="bkmrk-1%E3%80%81%E6%80%BB%E4%BD%93%E6%B5%81%E7%A8%8B">1、总体流程</div><div id="bkmrk-2%E3%80%81%E6%B6%88%E8%B4%B9%E8%80%85%E7%BB%84%C2%A0">2、消费者组 </div><div id="bkmrk-3%E3%80%81%E6%8C%89%E7%85%A7%E4%B8%BB%E9%A2%98%E6%B6%88%E8%B4%B9">3、按照主题消费</div><div id="bkmrk-0%EF%BC%89%E9%85%8D%E7%BD%AE%E4%BF%A1%E6%81%AF">0）配置信息</div><div id="bkmrk-%E8%BF%9E%E6%8E%A5">连接</div><div id="bkmrk-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96">反序列化</div><div id="bkmrk-%E7%BB%84id%C2%A0">组id </div><div id="bkmrk--21"></div><div id="bkmrk-1%EF%BC%89%E5%88%9B%E5%BB%BA%E6%B6%88%E8%B4%B9%E8%80%85">1）创建消费者</div><div id="bkmrk-2%EF%BC%89%E8%AE%A2%E9%98%85%E4%B8%BB%E9%A2%98">2）订阅主题</div><div id="bkmrk--22"></div><div id="bkmrk-3%EF%BC%89%E6%B6%88%E8%B4%B9%E6%95%B0%E6%8D%AE">3）消费数据</div><div id="bkmrk-4%E3%80%81%E6%8C%89%E7%85%A7%E5%88%86%E5%8C%BA%C2%A0">4、按照分区 </div><div id="bkmrk-5%E3%80%81%E6%B6%88%E8%B4%B9%E8%80%85%E7%BB%84%E6%A1%88%E4%BE%8B">5、消费者组案例</div><div id="bkmrk-%E7%BB%84id%C2%A0-0">组id </div><div id="bkmrk-6%E3%80%81%E5%88%86%E5%8C%BA%E5%88%86%E9%85%8D%E7%AD%96%E7%95%A5%C2%A0-%E5%86%8D%E5%B9%B3%E8%A1%A1">6、分区分配策略 再平衡</div><div id="bkmrk-7%E4%B8%AA%E5%88%86%E5%8C%BA%C2%A0-3%E4%B8%AA%E6%B6%88%E8%B4%B9%E8%80%85">7个分区 3个消费者</div><div id="bkmrk--23"></div><div id="bkmrk-range">range</div><div id="bkmrk-0%C2%A0-1-2-x">0 1 2 x</div><div id="bkmrk-3-4%C2%A0">3 4 </div><div id="bkmrk-5-6%C2%A0">5 6 </div><div id="bkmrk--24"></div><div id="bkmrk--25"></div><div id="bkmrk-roundrobin">roundrobin</div><div id="bkmrk-%E8%BD%AE%E8%AF%A2">轮询</div><div id="bkmrk-0%C2%A0-3%C2%A0-6">0 3 6</div><div id="bkmrk-1%C2%A0-4">1 4</div><div id="bkmrk-2%C2%A0-5">2 5</div><div id="bkmrk--26"></div><div id="bkmrk--27"></div><div id="bkmrk--28"></div><div id="bkmrk-%E7%B2%98%E6%80%A7%C2%A0-2-2-3%C2%A0">粘性 2 2 3 </div><div id="bkmrk-0%C2%A0-3-4">0 3 4</div><div id="bkmrk-2%C2%A0-6">2 6</div><div id="bkmrk-1-5%C2%A0">1 5 </div><div id="bkmrk-7%E3%80%81offset">7、offset</div><div id="bkmrk-1%EF%BC%89%E9%BB%98%E8%AE%A4%E5%AD%98%E5%82%A8%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%BB%E9%A2%98">1）默认存储在系统主题</div><div id="bkmrk-2%EF%BC%89%E8%87%AA%E5%8A%A8%E6%8F%90%E4%BA%A4%C2%A0-5s%C2%A0-%C2%A0%E9%BB%98%E8%AE%A4%C2%A0">2）自动提交 5s 默认 </div><div id="bkmrk-3%EF%BC%89%E6%89%8B%E5%8A%A8%E6%8F%90%E4%BA%A4%C2%A0-%E5%90%8C%E6%AD%A5%C2%A0-%E5%BC%82%E6%AD%A5%C2%A0%C2%A0">3）手动提交 同步 异步 </div><div id="bkmrk-4%EF%BC%89%E6%8C%87%E5%AE%9Aoffset%E6%B6%88%E8%B4%B9%C2%A0-seek-%28">4）指定offset消费 seek ()</div><div id="bkmrk-5%EF%BC%89%E6%8C%89%E7%85%A7%E6%97%B6%E9%97%B4%E6%B6%88%E8%B4%B9%C2%A0%C2%A0">5）按照时间消费 </div><div id="bkmrk-6%EF%BC%89%E6%BC%8F%E6%B6%88%E8%B4%B9%C2%A0-%E9%87%8D%E5%A4%8D%E6%B6%88%E8%B4%B9">6）漏消费 重复消费</div><div id="bkmrk-8%E3%80%81%E4%BA%8B%E5%8A%A1%C2%A0">8、事务 </div><div id="bkmrk-%E7%94%9F%E4%BA%A7%E7%AB%AF%C2%A0-%3D%E3%80%8B-%E9%9B%86%E7%BE%A4">生产端 =》 集群</div><div id="bkmrk-%E9%9B%86%E7%BE%A4-%3D%E3%80%8B-%E6%B6%88%E8%B4%B9%E8%80%85">集群 =》 消费者</div><div id="bkmrk-%E6%B6%88%E8%B4%B9%E8%80%85-%3D%E3%80%8B-%E6%A1%86%E6%9E%B6">消费者 =》 框架</div><div id="bkmrk-9%E3%80%81%E6%95%B0%E6%8D%AE%E7%A7%AF%E5%8E%8B">9、数据积压</div><div id="bkmrk-1%E3%80%81%E5%A2%9E%E5%8A%A0%E5%88%86%E5%8C%BA-%E5%A2%9E%E5%8A%A0%E6%B6%88%E8%B4%B9%E8%80%85%E4%B8%AA%E6%95%B0">1、增加分区 增加消费者个数</div><div id="bkmrk-2%E3%80%81%E7%94%9F%E4%BA%A7%C2%A0-%3D%E3%80%8B-%E9%9B%86%E7%BE%A4%C2%A0-4%E4%B8%AA%E5%8F%82%E6%95%B0">2、生产 =》 集群 4个参数</div><div id="bkmrk-3%E3%80%81%E6%B6%88%E8%B4%B9%E7%AB%AF%C2%A0-%E4%B8%A4%E4%B8%AA%E5%8F%82%E6%95%B0%C2%A0-50m%C2%A0-%C2%A05">3、消费端 两个参数 50m 500条</div><div id="bkmrk--29"></div><div id="bkmrk-%E5%85%AD%E3%80%81%E7%94%9F%E4%BA%A7%E8%B0%83%E4%BC%98-%E7%A1%AC%E4%BB%B6%E9%80%89%E6%8B%A9">六、生产调优 硬件选择</div><div id="bkmrk-1%E3%80%81100%E4%B8%87%E6%97%A5%E6%B4%BB-%2A-%E6%B2%A1%E4%BA%BA%E6%AF%8F%E5%A4%A9%E4%BA%A7%E7%94%9F%E6%97%A5%E5%BF%971">1、100万日活 * 没人每天产生日志100条 = 1亿条 （中型公司）</div><div id="bkmrk-%E5%A4%84%E7%90%86%E6%97%A5%E5%BF%97%E9%80%9F%E5%BA%A6%C2%A0-1%E4%BA%BF%E6%9D%A1-%2F-%2824-%2A-">处理日志速度 1亿条 / (24 * 3600s ) = 1150条/s </div><div id="bkmrk-1%E6%9D%A1%E6%97%A5%E5%BF%97-%EF%BC%880.5k---2k-1k%EF%BC%89">1条日志 （0.5k - 2k 1k）</div><div id="bkmrk-1150%E6%9D%A1-%2A-1k-%2Fs%C2%A0-%3D%C2%A0-1m">1150条 * 1k /s = 1m/s </div><div id="bkmrk--30"></div><div id="bkmrk-%E9%AB%98%E5%B3%B0%E5%80%BC-%EF%BC%88%E4%B8%AD%E5%8D%88%E5%B0%8F%E9%AB%98%E5%B3%B0-8--12-%EF%BC%89%EF%BC%9A-">高峰值 （中午小高峰 8 -12 ）： 1m/s * 20倍 = 20m/s -40m/s </div><div id="bkmrk--31"></div><div id="bkmrk-2%E3%80%81%E8%B4%AD%E4%B9%B0%E5%A4%9A%E5%B0%91%E5%8F%B0%E6%9C%8D%E5%8A%A1%E5%99%A8">2、购买多少台服务器</div><div id="bkmrk-%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%8F%B0%E6%95%B0%3D-2-%2A-%EF%BC%88%E7%94%9F%E4%BA%A7%E8%80%85%E5%B3%B0%E5%80%BC%E7%94%9F%E4%BA%A7%E9%80%9F">服务器台数= 2 * （生产者峰值生产速率 * 副本数 / 100） + 1 </div><div id="bkmrk-%C2%A0-%C2%A0%3D%C2%A0-2%C2%A0-%2A-%EF%BC%8820m%2Fs-%2A-"> = 2 * （20m/s * 2 /100） + 1</div><div id="bkmrk-%C2%A0-%C2%A0%3D-3-%E5%8F%B0"> = 3 台</div><div id="bkmrk--32"></div><div id="bkmrk-3%E3%80%81%E7%A3%81%E7%9B%98%E9%80%89%E6%8B%A9">3、磁盘选择</div><div id="bkmrk-kafka-%E6%8C%89%E7%85%A7%E9%A1%BA%E5%BA%8F%E8%AF%BB%E5%86%99%C2%A0-%C2%A0%E6%9C%BA%E6%A2%B0%E7%A1%AC%E7%9B%98%E5%92%8C">kafka 按照顺序读写 机械硬盘和固态硬盘 顺序读写速度差不多</div><div id="bkmrk--33"></div><div id="bkmrk-1%E4%BA%BF%E6%9D%A1%C2%A0-%2A%C2%A0-1k-%3D-100g%C2%A0">1亿条 * 1k = 100g </div><div id="bkmrk-100g-%2A-2%E4%B8%AA%E5%89%AF%E6%9C%AC-%2A-3%E5%A4%A9-%2F-0">100g * 2个副本 * 3天 / 0.7 = 1t</div><div id="bkmrk-%E5%BB%BA%E8%AE%AE%E4%B8%89%E5%8F%B0%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%80%BB%E7%9A%84%E7%A3%81%E7%9B%98%E5%A4%A7%E5%B0%8F%C2%A0-%E5%A4%A7%E4%BA%8E1t%C2%A0">建议三台服务器总的磁盘大小 大于1t </div><div id="bkmrk--34"></div><div id="bkmrk-4%E3%80%81%E5%86%85%E5%AD%98%E9%80%89%E6%8B%A9">4、内存选择</div><div id="bkmrk-kafka%C2%A0-%E5%86%85%E5%AD%98-%3D-%E5%A0%86%E5%86%85%E5%AD%98%EF%BC%88kafk">kafka 内存 = 堆内存（kafka 内部配置） + 页缓存（服务器内存）</div><div id="bkmrk-1%EF%BC%89%E5%A0%86%E5%86%85%E5%AD%98-10--15g">1）堆内存 10 -15g</div><div id="bkmrk-2%EF%BC%89%E9%A1%B5%E7%BC%93%E5%AD%98%C2%A0-segment-%281g-%29">2）页缓存 segment (1g ) （分区数Leader（10） * 1g * 25%）/ 3 = 1g</div><div id="bkmrk-%E4%B8%80%E5%8F%B0%E6%9C%8D%E5%8A%A1%E5%99%A8-10g-%2B-1g%C2%A0">一台服务器 10g + 1g </div><div id="bkmrk--35"></div><div id="bkmrk-5%E3%80%81cpu%E9%80%89%E6%8B%A9">5、CPU选择</div><div id="bkmrk-32cpu">32cpu</div><div id="bkmrk-6%E3%80%81%E7%BD%91%E7%BB%9C%E9%80%89%E6%8B%A9">6、网络选择</div><div id="bkmrk--36"></div><div id="bkmrk-%E6%B5%8B%E8%AF%95%EF%BC%9A">测试：</div><div id="bkmrk-1%E3%80%81batch.size%3D16384-l">1、batch.size=16384 linger.ms=0 9.76 MB/sec</div><div id="bkmrk-2%E3%80%81batch.size%3D32768-l">2、batch.size=32768 linger.ms=0 9.76 MB/sec</div><div id="bkmrk-3%E3%80%81batch.size%3D4096-li">3、batch.size=4096 linger.ms=0 3.81 MB/sec</div><div id="bkmrk-4%E3%80%81batch.size%3D4096-li">4、batch.size=4096 linger.ms=50 3.83 MB/sec</div><div id="bkmrk-5%E3%80%81batch.size%3D4096-li">5、batch.size=4096 linger.ms=50 compression.type=snappy 3.77 MB/sec</div><div id="bkmrk-6%E3%80%81batch.size%3D4096-li">6、batch.size=4096 linger.ms=50 compression.type=zstd 5.68 MB/sec</div><div id="bkmrk-7%E3%80%81batch.size%3D4096-li">7、batch.size=4096 linger.ms=50 compression.type=gzip 5.90 MB/sec</div><div id="bkmrk-8%E3%80%81batch.size%3D4096-li">8、batch.size=4096 linger.ms=50 compression.type=lz4 3.72 MB/sec</div><div id="bkmrk-9%E3%80%81batch.size%3D4096-li">9、batch.size=4096 linger.ms=50 buffer.memory=67108864 3.76 MB/sec</div><div id="bkmrk--37"></div><div id="bkmrk--38"></div><div id="bkmrk-%E6%B6%88%E8%B4%B9%E8%80%85%C2%A0-%E4%B8%80%E6%AC%A1%E5%A4%84%E7%90%86500%E6%9D%A1%C2%A0-%C2%A081.2">消费者 一次处理500条 81.2066m/s </div><div id="bkmrk-%E6%B6%88%E8%B4%B9%E8%80%85%C2%A0-%E4%B8%80%E6%AC%A1%E5%A4%84%E7%90%862000%E6%9D%A1%C2%A0-138.">消费者 一次处理2000条 138.0992m/s </div><div id="bkmrk-%E6%B6%88%E8%B4%B9%E8%80%85%C2%A0-%E4%B8%80%E6%AC%A1%E5%A4%84%E7%90%862000%E6%9D%A1%C2%A0-fetc">消费者 一次处理2000条 fetch.max.bytes=104857600 145.2033m/s</div><div id="bkmrk--39"></div><div id="bkmrk--40"></div><div id="bkmrk--41"></div><div id="bkmrk--42"></div><div id="bkmrk--43"></div><div id="bkmrk--44"></div><div id="bkmrk--45"></div><div id="bkmrk--46"></div><div id="bkmrk--47"></div><div id="bkmrk--48"></div><div id="bkmrk--49"></div><div id="bkmrk--50"></div><div id="bkmrk--51"></div><div id="bkmrk--52"></div><div id="bkmrk--53"></div><div id="bkmrk--54"></div><div id="bkmrk--55"></div><div id="bkmrk--56"></div><div id="bkmrk--57"></div><div id="bkmrk--58"></div><div id="bkmrk--59"></div><div id="bkmrk--60"></div><div id="bkmrk--61"></div><div id="bkmrk--62"></div><div id="bkmrk--63"></div><div id="bkmrk--64"></div>

# producer_comsumer範例

gradle

```INI
	// kafka dependency
	// https://mvnrepository.com/artifact/org.apache.kafka/kafka-clients
	implementation("org.springframework.kafka:spring-kafka")
	// https://mvnrepository.com/artifact/org.slf4j/slf4j-api
	implementation("org.slf4j:slf4j-api:2.0.5")
	// https://mvnrepository.com/artifact/org.slf4j/slf4j-simple
	implementation("org.slf4j:slf4j-simple:2.0.5")
	testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.2")
	testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
```

properties

```INI
kafka.listen.start:true
spring.application.name=kafka_demo
spring.kafka.bootstrap-servers=10.20.30.40:9092
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.properties.retries=10
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.group-id=group1
```

producer

```Java

@Controller
@RequestMapping("/api")
public class ProducerController {


    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    @GetMapping("/kafka")
    public ResponseEntity<String> send() throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            kafkaTemplate.send("first","data"+i,"data"+i);
//            Thread.sleep(1000);
        }
        return ResponseEntity.ok("{success:true}");
    }

}

```

consumer

```Java
package com.momo.appteam.liveapi.listener;

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.annotation.PartitionOffset;
import org.springframework.kafka.annotation.TopicPartition;
import org.springframework.stereotype.Component;

@Component
public class LiveStreamKafkaListener {

    @KafkaListener(topics = "first", autoStartup = "${kafka.listen.start:false}")
    public void consumeLiveStream(ConsumerRecord record)
    { // 参数: 收到的 value
//        System.out.println("收到的信息: " + record.toString());
        Object topic = record.topic();
        Object key = record.key();
        Object value = record.value();
        System.out.println("收到的信息: 【topic】" + topic + "【key】" + key + "【value】" + value );
        //ConsumerRecord(
        // topic = momoLiveStream, partition = 0,
        // leaderEpoch = 0, offset = 760,
        // CreateTime = 1697695508640,
        // serialized key size = 6,
        // serialized value size = 9,
        // headers = RecordHeaders(headers = [], isReadOnly = false),
        // key = liveId, value = afasffasf
        // )
    }
    @KafkaListener(
            groupId = "default",
            autoStartup = "${kafka.listen.start:false}" ,
            topicPartitions = {
                @TopicPartition(
                        topic = "first",

                        partitionOffsets = {
                            @PartitionOffset(partition = "0",initialOffset = "0")
                        }
                )
    })
    public void consumeAllLiveStream(ConsumerRecord record){
        Object topic = record.topic();
        Object key = record.key();
        Object value = record.value();
        System.out.println("收到的信息: 【topic】" + topic + "【key】" + key + "【value】" + value );
    }

}

```

# kafka connect

[架設Kafka與Kafka Connect讓訊息自動同步至MongoDB](https://notes.andywu.tw/2022/%E6%9E%B6%E8%A8%ADkafka%E8%88%87kafka-connect%E8%AE%93%E8%A8%8A%E6%81%AF%E8%87%AA%E5%8B%95%E5%90%8C%E6%AD%A5%E8%87%B3mongodb/)

connect 說明

[https://docs.confluent.io/platform/current/connect/index.html#connectors](https://docs.confluent.io/platform/current/connect/index.html#connectors)

下載 sink

[https://www.confluent.io/product/connectors/?\_ga=2.24281594.1343452646.1705657587-1925276915.1697595506&amp;\_gl=1\*1ibzdsy\*\_ga\*MTkyNTI3NjkxNS4xNjk3NTk1NTA2\*\_ga\_D2D3EGKSGD\*MTcwNTY1NzU4Ni4xMy4xLjE3MDU2NTc2MDQuNDIuMC4w](https://www.confluent.io/product/connectors/?_ga=2.24281594.1343452646.1705657587-1925276915.1697595506&_gl=1*1ibzdsy*_ga*MTkyNTI3NjkxNS4xNjk3NTk1NTA2*_ga_D2D3EGKSGD*MTcwNTY1NzU4Ni4xMy4xLjE3MDU2NTc2MDQuNDIuMC4w)

https://www.confluent.io/hub/mongodb/kafka-connect-mongodb

```JSON
{
	"connector.class": "com.mongodb.kafka.connect.MongoSinkConnector",
	"key.converter.schemas.enable": "false",
	"database": "momoLiveDB",
	"topics": "momoLiveStream",
	"namespace.mapper.value.collection.field": "order",
	"value.converter.schemas.enable": "false",
	"connection.uri": "mongodb://10.20.43.200:27017/",
	"name": "appLiveStream",
	"value.converter": "org.apache.kafka.connect.json.JsonConverter",
	"namespace.mapper": "com.mongodb.kafka.connect.sink.namespace.mapping.FieldPathNamespaceMapper",
	"key.converter": "org.apache.kafka.connect.storage.StringConverter"
}
```

# DockerCompose

# 【DockerCompose】Redis

#### [【Redis】用Docker-Compose搭建Redis Sentinel](https://lipeng1667.github.io/2021/06/21/set-up-redis-with-replication-and-sentinel-using-docker-compose/ "【Redis】用Docker-Compose搭建Redis Sentinel")

https://lipeng1667.github.io/2021/06/21/set-up-redis-with-replication-and-sentinel-using-docker-compose/

# 【DockerCompose】hoppscotch 安裝

使用線上版 [https://hoppscotch.io/](https://hoppscotch.io/)

## 確認安裝套件 (安裝 Node.js、npm、pnpm)

##### [https://docs.hoppscotch.io/documentation/self-host/community-edition/prerequisites](https://docs.hoppscotch.io/documentation/self-host/community-edition/prerequisites)

## 安裝pnpm

---

### ✅ 最推薦：使用 `curl` 官方安裝腳本

```bash
curl -fsSL https://get.pnpm.io/install.sh | sh -

```

安裝完後，`pnpm` 會被安裝到：

- `~/.local/share/pnpm`
- `~/.local/share/pnpm/pnpm` 是主程式
- 也會把 `~/.local/share/pnpm` 加到你的 `PATH` 環境變數中（需重新開機或重新登入 shell）

> 如果你沒有自動加到 PATH，可手動加在 `.bashrc` 或 `.zshrc`：

```bash
export PATH="$HOME/.local/share/pnpm:$PATH"

```

然後重新載入：

```bash
source ~/.bashrc
# 或者
source ~/.zshrc

```

---

### 👇 替代方式：透過 `npm` 安裝（需先裝好 Node.js）

如果你已經有 Node.js 和 npm：

```bash
npm install -g pnpm

```

---

### 🚀 驗證是否安裝成功

```bash
pnpm -v

```

會輸出版本號，例如：

```
9.1.2

```

---

### ✅ 附註：如果你還沒裝 Node.js

建議搭配 `nvm`（Node Version Manager）來安裝 Node.js，再用 `npm` 裝 `pnpm`：

```bash
# 安裝 nvm（Node.js 管理工具）
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

# 載入 nvm
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

# 安裝 Node.js（例如最新版）
nvm install node

# 再裝 pnpm
npm install -g pnpm

```

---

## build Image &amp;&amp; container

```shell
# clone 專案
git clone https://github.com/hoppscotch/hoppscotch.git

cd hoppscotch
# 確認port沒衝突(有的話要修改docker-compose.yml => ports)
hoppscotch-aio:
    profiles: ["default"]
    container_name: hoppscotch-aio
    restart: unless-stopped
    build:
      dockerfile: prod.Dockerfile
      context: .
      target: aio
    env_file:
      - ./.env
    depends_on:
      hoppscotch-db:
        condition: service_healthy
    ports:
      - "3005:3000"
      - "3100:3100"
      - "3170:3170"
      - "3200:3200"
      - "3080:80"

# 使用default 類型(profiles: ["default"])(all in one)
docker compose --profile default up -d
```

---

<span>成功運行必要的容器後，下一步是建立管理員帳戶來管理 Hoppscotch。  
</span>系統會自動將第一個透過管理儀表板登入的使用者指定為管理員。

<div data-callout-type="warning" id="bkmrk-%E5%BB%BA%E7%AB%8B%E7%AE%A1%E7%90%86%E5%93%A1%E5%B8%B3%E8%99%9F"><div><span style="color: #222222; font-size: 2.8275em; font-weight: 400;">建立管理員帳號</span></div></div>1. <span>開啟新的瀏覽器標籤並造訪</span>`http://localhost:3100`<span>。</span>
2. <span>這將授予您存取管理儀表板的權限。</span>
3. <span>使用您的憑證登入或建立一個新帳戶。</span>
4. <span>第一個登入的使用者將被賦予管理員權限。</span>

## <span class="cursor-pointer"><span>訪問 Hoppscotch 應用程式</span></span>

<span>設定管理員帳戶後，您現在可以開始使用 Hoppscotch 應用進行 API 測試和開發。</span>

1. <span>開啟新的瀏覽器標籤並造訪</span>[`http://localhost:3000`](http://localhost:3000/)<span>。</span>
2. <span>開始使用 Hoppscotch 無縫測試和開發您的 API。</span>

# 【DockerCompose】nodejs

```Nginx
# 使用官方 Node.js 映像作為基礎映像
FROM node:18-alpine AS builder

# 設置工作目錄
WORKDIR /app

# 複製 package.json 和 package-lock.json
COPY package.json package-lock.json ./

# 使用 npm ci 來安裝生產環境的依賴，這樣可以確保安裝的依賴版本與 package-lock.json 中一致
RUN npm config set registry http://10.40.137.10:8080/repository/npm_momo/
RUN npm ci

# 複製所有專案文件
COPY . .

# 構建 Next.js 專案
RUN npm run build:uat

# 使用多階段構建，減少最終映像大小
FROM node:18-alpine AS runner

# 設置工作目錄
WORKDIR /app

# 從 builder 階段複製 standalone 輸出
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public

# 設置環境變數
ENV NODE_ENV=production

# 暴露端口（Next.js 默認使用 3000 端口）
EXPOSE 3000

# 運行 Next.js 專案
CMD ["node", "server.js"]

```

# PowerShell

# 【PowerShell】相關資源

別人整理的筆記  
[https://sdwh.dev/posts/2020/09/PowerShell-CheatSheet/](https://sdwh.dev/posts/2020/09/PowerShell-CheatSheet/)

# 稍後閱讀

# wordpress 架站

wordpress 架站  
[https://www.youtube.com/@notesstartup](https://www.youtube.com/@notesstartup)

WordPress 購物網站教學 2024｜如何自己架設 WooCommerce 電商網站賣東西  
[https://notesstartup.com/wordpress-ecommerce-website-tutorial/](https://notesstartup.com/wordpress-ecommerce-website-tutorial/)

# 【轉載】2022 iThome 鐵人賽網頁開發小小地圖

來源：[https://ithelp.ithome.com.tw/articles/10286463](https://ithelp.ithome.com.tw/articles/10286463)

# 2022 iThome 鐵人賽網頁開發小小地圖

---

## 前端三本柱

#### 從0開始系列

- [【每天5分鐘】學前端](https://ithelp.ithome.com.tw/users/20151346/ironman/5507)`by Olivia`
- [手把手web初學者](https://ithelp.ithome.com.tw/users/20151549/ironman/5308) `by 我是誰我在哪...`
- [商科生初探網頁全端開發](https://ithelp.ithome.com.tw/users/20151031/ironman/5053) `by muller0219`
- [從零開始成為前端工程師 ](https://ithelp.ithome.com.tw/users/20151120/ironman/5089)`by 田田`
- [網頁前端設計](https://ithelp.ithome.com.tw/users/20151992/ironman/5477) `by Alexia`
- [網頁開發（html.css）](https://ithelp.ithome.com.tw/users/20120843/ironman/5158) `by claireyang`

#### HTML

- [一日一屬性，HTML 基礎一定行](https://ithelp.ithome.com.tw/users/20120833/ironman/5553)`by Keddie`
- [學習HTML的快樂路程](https://ithelp.ithome.com.tw/users/20152215/ironman/5601)`未完賽`(計30篇)

#### CSS

- [30天的網頁學習](https://ithelp.ithome.com.tw/users/20152010/ironman/5490) `by CCP7025`
- [CSS 面試趣](https://ithelp.ithome.com.tw/users/20151415/ironman/5466) `by Casey`
- [防禦性 CSS - 建立「防患未然」的匠人心態](https://ithelp.ithome.com.tw/users/20111490/ironman/4952) `by Taiming`

##### SASS/SCSS

- [關於SASS/SCSS](https://ithelp.ithome.com.tw/users/20151717/ironman/5757) `by weiiiiiiii`

##### bootstrap

- [不斷片(篇)挑戰](https://ithelp.ithome.com.tw/users/20151528/ironman/5364)`by fish_opera`

##### tailwind

- [從零開始的 Tailwind CSS 學習之旅](https://ithelp.ithome.com.tw/users/20152047/ironman/5536) `by 肉鬆`

##### Master CSS

- [學習 Master CSS，讓開發更專注](https://ithelp.ithome.com.tw/users/20103817/ironman/5509) `by 秘密基地`

#### JavaScript (ECMAScript)

##### 從基礎開始

- [\[學習筆記\] 邊學邊寫 JavaScript 30天](https://ithelp.ithome.com.tw/users/20145552/ironman/5213) `by LeeFang`
- [JavaScript101與人生幹話](https://ithelp.ithome.com.tw/users/20152166/ironman/5575) `by a121515222`
- [Javascripts惡補小教室](https://ithelp.ithome.com.tw/users/20141087/ironman/5302) `by karta1366796`
- [想轉職的鯊魚從零基礎開始學習JavaScript](https://ithelp.ithome.com.tw/users/20143214/ironman/5190) `by nikihypocrite`
- [喜歡打程式嗎？從JavaScript基礎來進行興趣初探!](https://ithelp.ithome.com.tw/users/20151124/ironman/5433) `by yting23`

##### 進階

- [JavaScript 之路，往前邁進吧！](https://ithelp.ithome.com.tw/users/20151114/ironman/5425) `by Vic`
- [JS 忍者訓練計畫](https://ithelp.ithome.com.tw/users/20107703/ironman/4997) `by J W Hu`
- [強化 JavaScript 之 - 程式語感是可以磨練成就的](https://ithelp.ithome.com.tw/users/20116883/ironman/4979) `by harry xie`
- [前端蛇行撞牆記](https://ithelp.ithome.com.tw/users/20152424/ironman/5659) `by jadddxx`
- [菜鳥前端修練之旅](https://ithelp.ithome.com.tw/users/20139057/ironman/5171) `by 佐料`
- [就是要搞懂 JavaScript 啦！](https://ithelp.ithome.com.tw/users/20118140/ironman/5799) `by Emi`
- [利用JS30逼自己30天完成的筆記](https://ithelp.ithome.com.tw/users/20128776/ironman/5835) `未完賽` (計25篇)

##### 資料結構或演算法

- [JavaScript - 30天 - 自學挑戰 ](https://ithelp.ithome.com.tw/users/20152092/ironman/5561)`by allenli`
- [刷題也算一種電競吧：演算法與資料結構](https://ithelp.ithome.com.tw/users/20123889/ironman/5322) `by jason71708`

##### 專案開發

- [Have fun! 新手也能打造的Javascript微型專案](https://ithelp.ithome.com.tw/users/20129072/ironman/5288) `by windate3411`
- [咩色用得好，歸剛沒煩惱 - 從 ECMAScript 偷窺 JavaScript Array method ](https://ithelp.ithome.com.tw/users/20152459/ironman/5744)`阿傑`
- [重造會 Slide 的輪子！深度解析 JavaScript + CSS 模組化設計](https://ithelp.ithome.com.tw/users/20111119/ironman/5224)` by Terry L.`

##### 畫布或其他

- [P5.js 學習之路](https://ithelp.ithome.com.tw/users/20152434/ironman/5663) `by misvamda`
- [Three.js 反閘之路](https://ithelp.ithome.com.tw/users/20142082/ironman/5555) `by Yingge170`
- [Three.js 學習日誌](https://ithelp.ithome.com.tw/users/20136559/ironman/5676) `by Mizok`
- [30天學習 3D網頁 - Three.js、spline](https://ithelp.ithome.com.tw/users/20151431/ironman/5343)`未完賽`(計30篇)

#### 三本柱綜合

- [做一個屬於自己的網頁](https://ithelp.ithome.com.tw/users/20151466/ironman/5276) `by chg200`
- [50後阿嬤教你寫程式！](https://ithelp.ithome.com.tw/users/20150979/ironman/5023) `by anquan`

---

## 前端三框架

#### Vue.js

- [vue3 + Leaflet.js](https://ithelp.ithome.com.tw/users/20150907/ironman/5004) `by xuancbb`
- [你知道這是什麼嗎？ Chrome Extension MV3 With Vite](https://ithelp.ithome.com.tw/users/20139636/ironman/4982) `by Winnie Wu`
- [使用 Vue 3 從 0 到 1 架設網站!!!](https://ithelp.ithome.com.tw/users/20069901/ironman/5248) `by Carlos Chang`
- [從零開始Vuejs](https://ithelp.ithome.com.tw/users/20151006/ironman/5233)`by godspeed246`
- [不用去柬埔寨也能活摘 Vue](https://ithelp.ithome.com.tw/users/20141148/ironman/5303)`by Hinata`
- [程式小白的 vue.js 學習筆記](https://ithelp.ithome.com.tw/users/20146827/ironman/5578) `by zihyinhsu`
- [教練我想做一個後台管理系統，阿我忘記我只有一個人沒有教練，那用試著以vue-pure-admin為基底做做看](https://ithelp.ithome.com.tw/users/20114417/ironman/5476) `by YUAN`
- [真的好想離開 Vue 3 新手村 feat. CompositionAPI](https://ithelp.ithome.com.tw/users/20152606/ironman/5782) `by 安揪拉`
- [因為拖延症而沒有好好準備有系統性文章架構的我只能靠一天一筆記來贖罪](https://ithelp.ithome.com.tw/users/20151931/ironman/5442)`by rocketpencil`

##### Nuxt

- [Nuxt 3 - 因為還不穩定，所以來踩地雷](https://ithelp.ithome.com.tw/users/20129729/ironman/5297) `by Genos`
- [Nuxt 3 學習筆記](https://ithelp.ithome.com.tw/users/20152617/ironman/5934) `by Ryan`

#### React.js

- [.NET Core與React組合開發技](https://ithelp.ithome.com.tw/users/20107452/ironman/5035) `by Samuel`
- [一步一腳印，這三年 JS 與 React 的學習分享](https://ithelp.ithome.com.tw/users/20112656/ironman/5042) `by Vita Ora`
- [終究都要學 React 何不現在學呢？](https://ithelp.ithome.com.tw/users/20119486/ironman/5111) `by Ray`
- [開始搞懂React生態系](https://ithelp.ithome.com.tw/users/20130417/ironman/5306) `by lala_lee_jobs`
- [你 React 了嗎? 30 天解鎖 React 技能 ](https://ithelp.ithome.com.tw/users/20115919/ironman/5602)`by Lala Wang`
- [我與 React 的 30天](https://ithelp.ithome.com.tw/users/20152148/ironman/5562) `by zhan`
- [新手前端與真實世界的開發 feat.React 與他的夥伴](https://ithelp.ithome.com.tw/users/20151978/ironman/5470) `by aoigj100`
- [進階前端網頁設計-初心者的30天React學習及應用](https://ithelp.ithome.com.tw/users/20152220/ironman/5674) `by morris2147`
- [React 新手村 - 填坑記](https://ithelp.ithome.com.tw/users/20151676/ironman/5694) `by kenliao0808`
- [為期 N 天的 react 小冒險](https://ithelp.ithome.com.tw/users/20112289/ironman/5721) `by dc88`
- [被討厭的前端實務手冊｜JS x TS x React](https://ithelp.ithome.com.tw/users/20139343/ironman/5813) `by Ryvn's Boss`
- [一次打破 React 常見的學習門檻與觀念誤解](https://ithelp.ithome.com.tw/users/20129300/ironman/5892) `by Zet`
- [React應用記錄誌](https://ithelp.ithome.com.tw/users/20139800/ironman/5071) `by maline77`
- [用React讓網頁動起來: React基礎與實作](https://ithelp.ithome.com.tw/users/20146829/ironman/5862) `by 時雨`
- [前端技術研究](https://ithelp.ithome.com.tw/users/20151426/ironman/5257) `by mollyhuang`
- [文科生轉職React前端工程師的菜鳥學習日記](https://ithelp.ithome.com.tw/users/20142045/ironman/5444) `by Cheryl`
- [React框架白話文運動](https://ithelp.ithome.com.tw/users/20152611/ironman/5780)`by hogantech`
- [我的React學習筆記](https://ithelp.ithome.com.tw/users/20140300/ironman/5113) `未完賽`(計30篇)
- [150 分鐘學 React](https://ithelp.ithome.com.tw/users/20129635/ironman/5424) `未完賽`(計31篇)

##### 元件/函式庫

- [React Hook 不求人，建立自己的 Hook Libary](https://ithelp.ithome.com.tw/users/20151334/ironman/5223) `by Charlie`
- [向網頁施點魔法粉 framer-motion](https://ithelp.ithome.com.tw/users/20138033/ironman/5519) `by Rosa`
- [從Create到React—用來實作使用者介面的JavaScript函式庫](https://ithelp.ithome.com.tw/users/20125095/ironman/5682) `by DannyChen`

##### Redux

- [30天深入淺出Redux](https://ithelp.ithome.com.tw/users/20129020/ironman/5360) `by LucianoLee`
- [新手進化日記，從React至Redux Saga](https://ithelp.ithome.com.tw/users/20152673/ironman/5863) `by whlshy`

#### AngularJS

- [菜雞們一起征服RxJS](https://ithelp.ithome.com.tw/users/20130155/ironman/5612) `by 小偉哥`
- [angular專案開發指南](https://ithelp.ithome.com.tw/users/20151497/ironman/5797) `by ShawnWu`

---

## 開發環境

#### 應用框架/版本控制

##### Node.js

- [Fastify 101](https://ithelp.ithome.com.tw/users/20151148/ironman/5108) `by yubintw`
- [Node.js 從零開始](https://ithelp.ithome.com.tw/users/20119300/ironman/5300) `by Tim Hsu 提姆寫程式`
- [用 Node.js 打造後端 API ](https://ithelp.ithome.com.tw/users/20151654/ironman/5349)`by nicolaswang`
- [利用30天學習JavaScript&amp;Node.js](https://ithelp.ithome.com.tw/users/20138060/ironman/5429) `by seaotter`
- [JavaScript node.js](https://ithelp.ithome.com.tw/users/20150733/ironman/5140) `by smitlea`
- [用Node.js建立專屬於你的API吧](https://ithelp.ithome.com.tw/users/20151565/ironman/5314) `by kylekao`

##### Git/GitHub

- [我的床邊故事Git and GitHub ](https://ithelp.ithome.com.tw/users/20150181/ironman/5032)`by yojijun`

##### 環境容器化

- [從異世界歸來發現只剩自己不會 Kubernetes](https://ithelp.ithome.com.tw/users/20149562/ironman/5366) `by mikehsu0618`
- [用 Docker 了嗎？ 30 天的 Docker 基本教學](https://ithelp.ithome.com.tw/users/20140124/ironman/5149) `by Robert.C`
- [從認識Docker到精通](https://ithelp.ithome.com.tw/users/20151816/ironman/5405) `by yen0224`

#### 實用套件

- [SPYxFRONTEND ～ 懂一點後端真是讓人哇哭哇哭 ](https://ithelp.ithome.com.tw/users/20129873/ironman/5274)`by Ado Lin`

#### 無障礙

- [無障礙網頁設計大叔日記](https://ithelp.ithome.com.tw/users/20152260/ironman/5614) `by 我愛無障礙網頁設計的大叔`

#### TypeScript

- [如何用TypeScript水30天鐵人賽](https://ithelp.ithome.com.tw/users/20109918/ironman/5363) `by Opshell`
- [你也對開始使用typescript感到無力嗎？我也是 - 30天初探typescript](https://ithelp.ithome.com.tw/users/20140522/ironman/5083) `by Lin Chen拎勸`
- [Hello TypeScript 菜鳥](https://ithelp.ithome.com.tw/users/20152385/ironman/5898) `by 2xL`

---

## 其他

#### UI/UX

- [產品設計師的 Chakra UI &amp; framer-motion 實作筆記](https://ithelp.ithome.com.tw/users/20151365/ironman/5227) `by indexhui`
- [30天UIUX自學之路](https://ithelp.ithome.com.tw/users/20151451/ironman/5897) `by Dolly`

#### 實作教學

- [30天數學老師作互動式教學網頁](https://ithelp.ithome.com.tw/users/20118113/ironman/5131) `by olddunk`
- [跳脫MVC，Laravel + React 建立電商網站](https://ithelp.ithome.com.tw/users/20145703/ironman/5290) `bennett`
- [laravel+vue 學習](https://ithelp.ithome.com.tw/users/20128127/ironman/5616) `by harrison9824003`
- [自己做一個價值幾十萬的動態網站，學會Mern開發、前台UI設計各式觀念與各式Lib、typescript你該學會的前端技術](https://ithelp.ithome.com.tw/users/20150652/ironman/5050) `by Ko`
- [訂單網頁嘗試集](https://ithelp.ithome.com.tw/users/20151423/ironman/5260) `by icetong`
- [從零開始打造炫砲個人部落格，使用 Next.js、ContentLayer、i18next 等現代技術](https://ithelp.ithome.com.tw/users/20130695/ironman/5397) `by EasonChang`
- [網頁切版入門及版面實作](https://ithelp.ithome.com.tw/users/20131898/ironman/5420) `by ruhui0726`
- [從 Open Source 專案學習 React 開發 － 以 lodestar-app 為例](https://ithelp.ithome.com.tw/users/20131877/ironman/5920) `by ruhui0726`
- [Full Stack Web Development 網站實作](https://ithelp.ithome.com.tw/users/20129584/ironman/5891) `by tjabi`
- [從零開始手刻網站，30 天打造我的前端武器庫](https://ithelp.ithome.com.tw/users/20129991/ironman/4968) `by 東尼先生`

#### 函式設計

- [致 JavaScript 開發者的 Functional Programming 新手指南](https://ithelp.ithome.com.tw/users/20151147/ironman/5232)`by HLTVProxy`

#### 全端混合

- [Vue+Django+MongoDB+Nginx 全端開發](https://ithelp.ithome.com.tw/users/20132990/ironman/4958) `by 哲嘉`
- [三十天全端工程師挑戰](https://ithelp.ithome.com.tw/users/20151340/ironman/5216) `by mafia`
- [LV的全端開發體驗](https://ithelp.ithome.com.tw/users/20131258/ironman/5514) `by mackliu`
- [分手前端菜雞之旅@非洲30天](https://ithelp.ithome.com.tw/users/20140247/ironman/5175)`by Global Rachel`
- [30天全端網頁學習之旅](https://ithelp.ithome.com.tw/users/20152607/ironman/5779) `by 黃昱翔`
- [30 天線上學前端](https://ithelp.ithome.com.tw/users/20151588/ironman/5452) `未完賽` (計50篇)

#### 職涯/面試甘苦談

- [擊敗前端面試！](https://ithelp.ithome.com.tw/users/20148825/ironman/5299) `by bywater529`

---

#### 待分類

- [挑戰typescript+react+golang+graphql 系列](https://ithelp.ithome.com.tw/users/20150497/ironman/5085)`by konchok3po`
- [我們可以不要 component library 了嗎？](https://ithelp.ithome.com.tw/users/20142759/ironman/5441)`by 查理狐狐`
- [2022年 JavaScript 相關應用及學習之繁體中文選](https://ithelp.ithome.com.tw/users/20046160/ironman/5015) `by Wolke`
- [派對動物嗨起來！](https://ithelp.ithome.com.tw/users/20140213/ironman/5661) `by 鱈魚`
- [關於用 Javascript (Typescript) Stack 打造某種 Backend 3D Rendering 的東東這檔事](https://ithelp.ithome.com.tw/users/20152626/ironman/5815) `by Wei Ji`
- [web component - 次世代網頁技術的重要拼圖](https://ithelp.ithome.com.tw/users/20131059/ironman/5888) `by BugofBook`
- [前端開發維護筆記 - 打造健康的前端專案](https://ithelp.ithome.com.tw/users/20129182/ironman/5931) `by Sid 吸管`
- [Modern Web前端三分鐘 X 從把妹角度理解前後端如何和平相處](https://ithelp.ithome.com.tw/users/20130026/ironman/5783) `by linyencheng`

---

## 後端/其他語言(待整理

#### PHP

###### Laravel

- [Laravel 9 漫遊，享受魔法般的極速網頁開發體驗](https://ithelp.ithome.com.tw/users/20120550/ironman/5098)`by ReccaChao`
- [我越來越靠近PHP囉 我要打給Laravel ](https://ithelp.ithome.com.tw/users/20149468/ironman/5298)`by 沃克`

#### RoR

- [從0開始刻 淺談 Rails 的運作魔法](https://ithelp.ithome.com.tw/users/20150947/ironman/4996) `by ning_chang`
- [一探紅寶石的神秘面紗 - Ruby 及 Rails入門介紹 系列](https://ithelp.ithome.com.tw/users/20150977/ironman/5049) `by Sean`
- [淺入淺出 Rails with Vue 系列](https://ithelp.ithome.com.tw/users/20151062/ironman/5063)`by k5953837`
- [Rails，我要進來囉](https://ithelp.ithome.com.tw/users/20124548/ironman/5416)`by Sam`

#### ASP.NET

- [C# ASP.NET MVC實作: 30天打造屬於你的網站應用程式](https://ithelp.ithome.com.tw/users/20150166/ironman/4959) `by Coding小僧`
- [Asp.Net Core 零基礎建立自己的Blog](https://ithelp.ithome.com.tw/users/20125853/ironman/4999) `by Wellinkers`
- [.NET教我做人](https://ithelp.ithome.com.tw/users/20139256/ironman/5304)`by KBN`
- \*[翻新ASP.NET Webform成Blazor Server App之可靠升級法透過Robot framework](https://ithelp.ithome.com.tw/users/20151458/ironman/5379)
- [抱 .Net Core](https://ithelp.ithome.com.tw/users/20109549/ironman/5512)`by Eric Xie`

###### Blazor

- [我與 Blazor 的 30 天 ](https://ithelp.ithome.com.tw/users/20151454/ironman/5285)`by Jim Wang`

#### Elixir

###### Phoenix

- [速成 Phoenix, 2022年最受喜愛框架](https://ithelp.ithome.com.tw/users/20141054/ironman/4953) `by 韋政`

#### Python

###### Django

- [傳承D的意志~ 邁向Django的偉大航道](https://ithelp.ithome.com.tw/users/20151096/ironman/5130)`by sean85120`
- [Django 初心者之旅](https://ithelp.ithome.com.tw/users/20135354/ironman/5546)`by Xiang1009`
- [從0 到 50 初探 如何使用Django 架構出一個網站](https://ithelp.ithome.com.tw/users/20150927/ironman/4977)`by abcd5101888`

###### flask

- [flask](https://ithelp.ithome.com.tw/users/20150239/ironman/5160)`by albert9137`

# web.dev

[https://web.dev/learn/css](https://web.dev/learn/css)

# Web API

https://ithelp.ithome.com.tw/users/20120631/ironman/7640

# ECMAScript 6 入门

# [ECMAScript 6 入门](https://es6.ruanyifeng.com/)

# 學習  Typescript

[https://www.typescriptlang.org/](https://www.typescriptlang.org/)

[https://ts.xcatliu.com/](https://ts.xcatliu.com/)

### 推薦學習資源

#### 1. 官方與經典教材 (權威必讀)

- **[TypeScript 官方文檔 (Handbook)](https://www.typescriptlang.org/docs/handbook/intro.html)**：最權威的起點。推薦先讀「TypeScript for JavaScript Programmers」，快速理解 TS 的核心哲學。
- **[TypeScript Deep Dive 中文版](https://jkchao.github.io/typescript-book-chinese/)**：目前公認最深入淺出的中文開源書，適合想要理解底層運作原理的人。
- **[TypeScript 入門教程](https://ts.xcatliu.com/)**：針對 JavaScript 背景開發者撰寫，非常符合中文母語者的閱讀習慣。

#### 2. 互動式與影片課程

- **[Total TypeScript](https://www.totaltypescript.com/)**：由 Matt Pocock 製作，是目前市面上最頂尖的進階實戰課程，特別強調 IDE (VS Code) 的協作體驗。
- **[Scrimba - Learn TypeScript](https://scrimba.com/learn/typescript)**：互動式影片教學，你可以直接在影片播放器裡改代碼，適合初學者建立肌肉記憶。
- **[六角學院 - TypeScript 實戰班](https://www.hexschool.com/courses/typescript-training.html)**：如果你偏好中文教學與社群討論，這門課結合了 AI 協作開發流程，非常實戰。

#### 3. 實戰練習與工具

- **[TypeScript Playground](https://www.typescriptlang.org/play)**：無需安裝環境，直接在瀏覽器測試型別邏輯，還能看編譯後的 JS。
- **[Type Challenges](https://github.com/type-challenges/type-challenges)**：想挑戰進階的「型別體操 (Type Gymnastics)」必去，收集了各等級的型別題目。

# K8s官方文檔

https://kubernetes.io/zh-cn/docs/concepts/

# modern-web-guidance

[https://developer.chrome.com/docs/modern-web-guidance](https://developer.chrome.com/docs/modern-web-guidance)

# IDE

# 【Xshell】快速命令按鈕

[![image-1732605634999.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-11/scaled-1680-/6zMT5CsSyWCRX4oy-image-1732605634999.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-11/6zMT5CsSyWCRX4oy-image-1732605634999.png)

# Nexus 相關

# Nexus 3 Docker倉庫（hosted、proxy、group）模式解說

出處：https://www.cnblogs.com/yjbjingcha/p/8308973.html

Nexus作為私庫管理最受歡迎的工具之中的一個，用於套件的管理和Docker映像管理的私庫管理場景中非經常常使用。 Easypack利用最新版本號的oss版Nexus作為基礎鏡像來提供相似服務。

本文將同一時候給出具體步驟結合最新發行的Docker-CE版本號實現鏡像私庫的管理。

# Why Nexus 3

這裡整理了為什麼使用Nexus 3的一些理由，在做選型的時候能夠做一個簡單參照。

<div id="bkmrk-%E5%B0%88%E6%A1%88-%E5%85%B7%E9%AB%94-%E7%82%BA%E4%BB%80%E9%BA%BC%E8%A6%81%E4%BD%BF%E7%94%A8nexus-3-"><div><table><thead><tr><th>專案</th><th>具體</th></tr></thead><tbody><tr><td>為什麼要使用Nexus 3</td><td>[http://blog.csdn.net/liumiaocn/article/details/62050525](http://blog.csdn.net/liumiaocn/article/details/62050525)</td></tr></tbody></table>

</div></div># docker版本號

本次使用的docker的版本號為17.03.0-ce，可是應該不限於此版本號，其它版本號未作驗證。

```
[root@liumiaocn ~]# docker version
Client:
 Version:      17.03.0-ce
 API version:  1.26
 Go version:   go1.7.5
 Git commit:   3a232c8
 Built:        Tue Feb 28 08:10:07 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.03.0-ce
 API version:  1.26 (minimum version 1.12)
 Go version:   go1.7.5
 Git commit:   3a232c8
 Built:        Tue Feb 28 08:10:07 2017
 OS/Arch:      linux/amd64
 Experimental: false
[root@liumiaocn ~]#
```

# 下載鏡像

```
[root@liumiaocn ~]# docker pull liumiaocn/nexus
Using default tag: latest
latest: Pulling from liumiaocn/nexus
Digest: sha256:b93f9a6bba2b35ada33c324cd06bd2c732fc1bed352df186af1a013e228af8d8
Status: Image is up to date for liumiaocn/nexus:latest
[root@liumiaocn ~]#
```

# 啟動Nexus

<table id="bkmrk-%E5%B0%88%E6%A1%88-%E5%85%B7%E9%AB%94-nexus-ui-8081-" style="height: 151px;"><thead><tr style="height: 29px;"><th style="height: 29px; width: 94.4375px;">專案</th><th style="height: 29px; width: 193.703px;">具體</th></tr></thead><tbody><tr style="height: 29px;"><td style="height: 29px; width: 94.4375px;">Nexus UI</td><td style="height: 29px; width: 193.703px;">8081</td></tr><tr style="height: 29px;"><td style="height: 29px; width: 94.4375px;">private repo</td><td style="height: 29px; width: 193.703px;">8082</td></tr><tr style="height: 29px;"><td style="height: 29px; width: 94.4375px;">proxy repo</td><td style="height: 29px; width: 193.703px;">8083</td></tr><tr style="height: 35px;"><td style="height: 35px; width: 94.4375px;">URL</td><td style="height: 35px; width: 193.703px;">[http://192.168.32.123:8081/](http://192.168.32.123:8081/)</td></tr></tbody></table>

```
[root@liumiaocn ~]# docker run -d -p 8081:8081 -p 8082:8082 -p 8083:8083 --name nexus liumiaocn/nexus
222abae47fcf9d32c821bff6426edd03f6757a3dd4cbe07517dada5d800e173f
[root@liumiaocn ~]#
```

# logon

<table id="bkmrk-%E5%B0%88%E6%A1%88-%E5%85%B7%E9%AB%94-url-http%3A%2F%2F192" style="height: 122px;"><thead><tr style="height: 29px;"><th style="height: 29px; width: 120.953px;">專案</th><th style="height: 29px; width: 193.703px;">具體</th></tr></thead><tbody><tr style="height: 35px;"><td style="height: 35px; width: 120.953px;">URL</td><td style="height: 35px; width: 193.703px;">[http://192.168.32.123:8081/](http://192.168.32.123:8081/)</td></tr><tr style="height: 29px;"><td style="height: 29px; width: 120.953px;">username稱</td><td style="height: 29px; width: 193.703px;">admin</td></tr><tr style="height: 29px;"><td style="height: 29px; width: 120.953px;">使用者password</td><td style="height: 29px; width: 193.703px;">admin123</td></tr></tbody></table>

[![image-1733280055752.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/QRq1TNlZw1hWExWX-image-1733280055752.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/QRq1TNlZw1hWExWX-image-1733280055752.png)

登陸後

[![image-1733280091016.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/G29WypfBZaAYXrr8-image-1733280091016.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/G29WypfBZaAYXrr8-image-1733280091016.png)

# 倉庫類型

具體倉庫類型主要分為hosted/proxy/group三種。

具體含義例如以下：

<div id="bkmrk-%E5%B0%88%E6%A1%88-%E5%85%B7%E9%AB%94%E8%AA%AA%E6%98%8E-hosted-%E6%9C%AC%E5%9C%B0%E5%84%B2%E5%AD%98%E3%80%82"><div><table><thead><tr><th>專案</th><th>具體說明</th></tr></thead><tbody><tr><td>hosted</td><td>本地儲存。像官方倉庫一樣提供本地私庫功能</td></tr><tr><td>proxy</td><td>提供代理其它倉庫的類型</td></tr><tr><td>group</td><td>群組類型，能夠組合多個倉庫為一個位址提供服務</td></tr></tbody></table>

</div></div># 建立priavete倉庫

建立一個Hosted的倉庫作為private倉庫，具體設定資訊例如以下：

<div id="bkmrk-%E5%B0%88%E6%A1%88-%E5%85%B7%E9%AB%94%E8%AA%AA%E6%98%8E-%E9%A1%9E%E5%9E%8B-docker-%28h"><div><table><thead><tr><th>專案</th><th>具體說明</th></tr></thead><tbody><tr><td>類型</td><td>docker (hosted)</td></tr><tr><td>Name</td><td>docker-repo-private</td></tr><tr><td>HTTP Port</td><td>8082</td></tr><tr><td>Blob store</td><td>docker-repo-private</td></tr><tr><td>Deployment policy</td><td>Allow redeploy</td></tr></tbody></table>

</div></div>[![image-1733280146393.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/j03IVMQxO5guwiwq-image-1733280146393.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/j03IVMQxO5guwiwq-image-1733280146393.png)

# 建立proxy倉庫

建立一個proxy倉庫。具體設定資訊例如以下：

<div id="bkmrk-%E5%B0%88%E6%A1%88-%E5%85%B7%E9%AB%94%E8%AA%AA%E6%98%8E-%E9%A1%9E%E5%9E%8B-docker-%28p"><div><table><thead><tr><th>專案</th><th>具體說明</th></tr></thead><tbody><tr><td>類型</td><td>docker (proxy)</td></tr><tr><td>Name</td><td>docker-repo-proxy</td></tr><tr><td>Location of the remote repository being proxied</td><td>[https://registry-1.docker.io](https://registry-1.docker.io/)</td></tr><tr><td>Docker Index</td><td>Use Docker Hub</td></tr><tr><td>Blob store</td><td>docker-repo-proxy</td></tr></tbody></table>

</div></div>[![image-1733280211579.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/auBxhCsthRjsdHKb-image-1733280211579.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/auBxhCsthRjsdHKb-image-1733280211579.png)

# 建立group倉庫<button aria-expanded="false" class="cnblogs-toc-button" title="顯示目錄導航"></button>

建立一個group倉庫。具體設定資訊例如以下：

<div id="bkmrk-%E5%B0%88%E6%A1%88-%E5%85%B7%E9%AB%94%E8%AA%AA%E6%98%8E-%E9%A1%9E%E5%9E%8B-docker-%28g"><div><table><thead><tr><th>專案</th><th>具體說明</th></tr></thead><tbody><tr><td>類型</td><td>docker (group)</td></tr><tr><td>Name</td><td>docker-repo-group</td></tr><tr><td>HTTP Port</td><td>8083</td></tr><tr><td>Blob store</td><td>docker-repo-group</td></tr><tr><td>Member repositories</td><td>docker-repo-private</td></tr><tr><td>Member repositories</td><td>docker-repo-proxy</td></tr></tbody></table>

</div></div>[![image-1733280254164.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/ryWgtsEoqw1lgUTy-image-1733280254164.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/ryWgtsEoqw1lgUTy-image-1733280254164.png)

# docker設定

Docker的私庫能夠使用HTTP或HTTPS。 Nexus 3都予以支持。本文的方式採用HTTP方式。因此須要設定docker。在docker啟動前設定例如以下資訊是須要的

<div id="bkmrk-%E5%B0%88%E6%A1%88-%E5%85%B7%E9%AB%94%E8%AA%AA%E6%98%8E-%E8%A8%AD%E5%AE%9A%E7%89%A9%E4%BB%B6%E6%96%87%E4%BB%B6-%2Fetc%2F"><table><thead><tr><th>專案</th><th>具體說明</th></tr></thead><tbody><tr><td>設定物件文件</td><td>/etc/docker/daemon.json</td></tr><tr><td>設定內容</td><td>insecure-registries</td></tr></tbody></table>

</div>設定具體內容。例如以下所看到的：

```
[root@liumiaocn ~]# cat /etc/docker/daemon.json
{
  "insecure-registries": [
    "192.168.32.123:8082",
    "192.168.32.123:8083"
  ],
  "disable-legacy-registry": true
}
[root@liumiaocn ~]#
```

# 重新啟動docker

```
[root@liumiaocn docker]# systemctl restart docker
[root@liumiaocn docker]#
```

# 啟動nexus

隨著docker的重新啟動。 nexus的容器也須要啟動。具體例如以下所看到的：

```
[root@liumiaocn docker]# docker start nexus
nexus
[root@liumiaocn docker]#
```

# 確認

至此，Nexus的設定準備基本就緒。能夠確認結果了。

## docker login

為了進行操作。須事先進行docker login的操作

<div id="bkmrk-%E5%B0%88%E6%A1%88-%E5%85%B7%E9%AB%94%E8%AA%AA%E6%98%8E-private%E5%80%89%E5%BA%AB-do"><table><thead><tr><th>專案</th><th>具體說明</th></tr></thead><tbody><tr><td>private倉庫</td><td>docker login -u admin -p admin123 192.168.32.123:8082</td></tr><tr><td>proxy倉庫</td><td>docker login -u admin -p admin123 192.168.32.123:8083</td></tr></tbody></table>

</div>運行確認

```
[root@liumiaocn ~]# docker login -u admin -p admin123 192.168.32.123:8082
Login Succeeded
[root@liumiaocn ~]# docker login -u admin -p admin123 192.168.32.123:8083
Login Succeeded
[root@liumiaocn ~]#
```

## proxy倉庫確認

從remote倉庫pull下來鏡像。然後確認是否在proxy倉庫中存在

```
[root@liumiaocn ~]# docker pull 192.168.32.123:8083/alpine:3.5
3.5: Pulling from alpine
627beaf3eaaf: Downloading
unknown blob
[root@liumiaocn docker]#
```

查了一下nexus的一些issue，發現有不少都是跟blob相關，看起來相關的小的問題另一些在不斷的收拾中，可是不影響結果。  
確認proxy倉庫，發現pull的alpine的3.5版本號已然在proxy倉庫中保存完成，所以上面的unknown blob也確實沒有影響結果。

[![image-1733280649584.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/e6WzyK7PjFO2KtCE-image-1733280649584.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/e6WzyK7PjFO2KtCE-image-1733280649584.png)

## private倉庫確認

### 事前鏡像確認

```
[root@liumiaocn ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
busybox             latest              00f017a8c2a6        2 days ago          1.11 MB
liumiaocn/maven     latest              833b66f10ce6        5 days ago          160 MB
liumiaocn/nexus     latest              932d715eb7e1        5 days ago          460 MB
liumiaocn/gitlab    latest              2462fb291203        5 days ago          1.21 GB
liumiaocn/jenkins   latest              6668ecd39e4f        5 days ago          293 MB
[root@liumiaocn ~]#
```

### tag busybox鏡像

```
[root@liumiaocn ~]# docker tag busybox 192.168.32.123:8082/busybox:latest
[root@liumiaocn ~]# docker images
REPOSITORY                    TAG                 IMAGE ID            CREATED             SIZE
192.168.32.123:8082/busybox   latest              00f017a8c2a6        2 days ago          1.11 MB
busybox                       latest              00f017a8c2a6        2 days ago          1.11 MB
liumiaocn/maven               latest              833b66f10ce6        5 days ago          160 MB
liumiaocn/nexus               latest              932d715eb7e1        5 days ago          460 MB
liumiaocn/gitlab              latest              2462fb291203        5 days ago          1.21 GB
liumiaocn/jenkins             latest              6668ecd39e4f        5 days ago          293 MB
[root@liumiaocn ~]#
```

### docker push

```
[root@liumiaocn ~]# docker push 192.168.32.123:8082/busybox:latest
The push refers to a repository [192.168.32.123:8082/busybox]
c0de73ac9968: Pushed
latest: digest: sha256:68effe31a4ae8312e47f54bec52d1fc925908009ce7e6f734e1b54a4169081c5 size: 527
[root@liumiaocn ~]#
```

結果確認：busybox鏡像已經被正常地push到了private倉庫中

[![image-1733280612437.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/ro3Zkl6BI0Bgx7sp-image-1733280612437.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/ro3Zkl6BI0Bgx7sp-image-1733280612437.png)

### <span>docker pull</span>

<span>為了確認docker pull的動作。事前先將先前的busybox先刪除，以確認確實下載了新的busybox鏡像到本地。</span>

```
[root@liumiaocn ~]# docker rmi busybox
Untagged: busybox:latest
Untagged: busybox@sha256:32f093055929dbc23dec4d03e09dfe971f5973a9ca5cf059cbfb644c206aa83f
[root@liumiaocn ~]# docker rmi 192.168.32.123:8082/busybox
Untagged: 192.168.32.123:8082/busybox:latest
Untagged: 192.168.32.123:8082/busybox@sha256:68effe31a4ae8312e47f54bec52d1fc925908009ce7e6f734e1b54a4169081c5
Deleted: sha256:00f017a8c2a6e1fe2ffd05c281f27d069d2a99323a8cd514dd35f228ba26d2ff
Deleted: sha256:c0de73ac99683640bc8f8de5cda9e0e2fc97fe53d78c9fd60ea69b31303efbc9
[root@liumiaocn ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
liumiaocn/maven     latest              833b66f10ce6        5 days ago          160 MB
liumiaocn/nexus     latest              932d715eb7e1        5 days ago          460 MB
liumiaocn/gitlab    latest              2462fb291203        5 days ago          1.21 GB
liumiaocn/jenkins   latest              6668ecd39e4f        5 days ago          293 MB
[root@liumiaocn ~]#
```

docker pull操作:能夠看到確實是從private倉庫下載下來了鏡像，並且速度也明顯快了非常之多。

```
[root@liumiaocn ~]# docker pull 192.168.32.123:8082/busybox
Using default tag: latest
latest: Pulling from busybox
04176c8b224a: Pull complete
Digest: sha256:68effe31a4ae8312e47f54bec52d1fc925908009ce7e6f734e1b54a4169081c5
Status: Downloaded newer image for 192.168.32.123:8082/busybox:latest
[root@liumiaocn ~]# docker images
REPOSITORY                    TAG                 IMAGE ID            CREATED             SIZE
192.168.32.123:8082/busybox   latest              00f017a8c2a6        2 days ago          1.11 MB
liumiaocn/maven               latest              833b66f10ce6        5 days ago          160 MB
liumiaocn/nexus               latest              932d715eb7e1        5 days ago          460 MB
liumiaocn/gitlab              latest              2462fb291203        5 days ago          1.21 GB
liumiaocn/jenkins             latest              6668ecd39e4f        5 days ago          293 MB
[root@liumiaocn ~]#
```

# <span>總結</span>

<span>至此，如何使用Nexus管理Docker映像已然十分清楚，可是怎樣非常好的管理鏡像，備份機制/版本號控制/差分管理/定期cache/及時清理等方面還有非常多問題須要考慮。須要在實際的工作中不斷實踐總結。</span>

# Nexus 常用設定

## Ｍaven 

### my\_maven\_proxy

[https://repo1.maven.org/maven2/](https://repo1.maven.org/maven2/)

[![image-1733281001672.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/L4HFzg8tZyqeDYKj-image-1733281001672.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/L4HFzg8tZyqeDYKj-image-1733281001672.png)

[![image-1733281035906.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/vefWCOpsKvVRodNr-image-1733281035906.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/vefWCOpsKvVRodNr-image-1733281035906.png)

### my\_gradle\_proxy

https://plugins.gradle.org/m2/

[![image-1733449890399.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/k94RVxn4SbRoeVhg-image-1733449890399.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/k94RVxn4SbRoeVhg-image-1733449890399.png)

[![image-1733449910064.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/Uc3Qm93QYNbSrtOt-image-1733449910064.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/Uc3Qm93QYNbSrtOt-image-1733449910064.png)

### my\_maven\_hosted

[![image-1733281074303.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/SjSVinB2wpYCHRVX-image-1733281074303.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/SjSVinB2wpYCHRVX-image-1733281074303.png)

### maven\_google

https://maven.google.com

[![image-1733281325902.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/1Goc7S90kM9tE469-image-1733281325902.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/1Goc7S90kM9tE469-image-1733281325902.png)

[![image-1733281348818.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/AUiscw20pIZOdmWv-image-1733281348818.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/AUiscw20pIZOdmWv-image-1733281348818.png)

### my\_group

[![image-1733281189611.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/AZ6xlwDXau9g9aAk-image-1733281189611.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/AZ6xlwDXau9g9aAk-image-1733281189611.png)

---

### my\_npm\_proxy

[https://registry.npmjs.org/](https://registry.npmjs.org/)

[![image-1733281496213.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/MyPuRfWoaOQxpZwN-image-1733281496213.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/MyPuRfWoaOQxpZwN-image-1733281496213.png)

[![image-1733281973365.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/tb3M5HpMLjbhloXI-image-1733281973365.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/tb3M5HpMLjbhloXI-image-1733281973365.png)

### my\_npm\_hosted

[![image-1733282083806.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/5qgeqq7dvSet3nh9-image-1733282083806.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/5qgeqq7dvSet3nh9-image-1733282083806.png)

### my\_python\_proxy

[https://pypi.python.org/](https://pypi.python.org/)

[![image-1733282279093.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/ur8x94IvuzAErPYO-image-1733282279093.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/ur8x94IvuzAErPYO-image-1733282279093.png)

[![image-1733282309257.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/XGIIbFjl9sBmbGw5-image-1733282309257.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/XGIIbFjl9sBmbGw5-image-1733282309257.png)

### my\_python\_hosts

[![image-1733282349745.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/2HIXIvy9vrR531Kf-image-1733282349745.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/2HIXIvy9vrR531Kf-image-1733282349745.png)

### my\_docker\_proxy

https://registry-1.docker.io

[![image-1733282623249.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/VVBaykEzASyLlJcQ-image-1733282623249.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/VVBaykEzASyLlJcQ-image-1733282623249.png)

[![image-1733282662273.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/MHqaIas3Zj7MWPXx-image-1733282662273.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/MHqaIas3Zj7MWPXx-image-1733282662273.png)

[![image-1733282684529.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/Lenf9IizepLm6odV-image-1733282684529.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/Lenf9IizepLm6odV-image-1733282684529.png)

### my\_docker\_hosted

[![image-1733282739250.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/1Xct4ZgvNIHZlc9N-image-1733282739250.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/1Xct4ZgvNIHZlc9N-image-1733282739250.png)

# Nexus 使用匿名下載(不用登入)

【Security】【Anonymous Access】【Access】打勾

[![image-1733450129929.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/scaled-1680-/g6rWZ3w7zUYSh0w4-image-1733450129929.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-12/g6rWZ3w7zUYSh0w4-image-1733450129929.png)

# Ｎexus  docker-compose.yml

```YAML
version: "3"
services:
  nexus:
    image: sonatype/nexus3
    restart: always
    volumes:
      - "./data/sonatype-work:/sonatype-work"
      - "./data/nexus-data:/nexus-data"
    ports:
      - "8088:8081"
      - "8089:8085"
volumes:
  nexus-data: {}
  
# 取得密碼 /nexus-data/admin.password
# sudo docker exec -it ubuntu_nexus_1 cat /nexus-data/admin.password

```

# 程式相關

# 相關資源

#### Learn X in Y minutes

[https://learnxinyminutes.com/](https://learnxinyminutes.com/)

# GoogleClould

# Pub/Sub 是什麼？6 大常見名詞與訊息生命週期介紹

<header class="entry-header clr" id="bkmrk-pub%2Fsub-%E6%98%AF%E4%BB%80%E9%BA%BC%EF%BC%9F6-%E5%A4%A7%E5%B8%B8%E8%A6%8B%E5%90%8D%E8%A9%9E%E8%88%87">#  

</header><div id="bkmrk-">![Pub/Sub 是什麼？6 大常見名詞與訊息生命週期介紹](https://storage.googleapis.com/cloudace-tw-blog/1/2024/01/Pub-Sub-%E6%98%AF%E7%94%9A%E9%BA%BC%EF%BC%9F6-%E5%A4%A7%E5%B8%B8%E8%A6%8B%E5%90%8D%E8%A9%9E%E8%88%87%E8%A8%8A%E6%81%AF%E7%94%9F%E5%91%BD%E9%80%B1%E6%9C%9F%E4%BB%8B%E7%B4%B9.jpg)</div>文章段落

<div id="bkmrk-pub%2Fsub-%E6%98%AF%E4%BB%80%E9%BA%BC%EF%BC%9F-%E6%A6%82%E8%BF%B0%E8%88%87%E5%84%AA%E5%8B%A2%E4%BB%8B%E7%B4%B9"><div><label class="ez-toc-cssicon-toggle-label" for="ez-toc-cssicon-toggle-item-67ab0004b68b0"><svg class="list-377408" fill="none" height="20px" viewbox="0 0 24 24" width="20px" xmlns="http://www.w3.org/2000/svg"></svg><svg baseprofile="tiny" class="arrow-unsorted-368013" height="10px" version="1.2" viewbox="0 0 24 24" width="10px" xmlns="http://www.w3.org/2000/svg"></svg></label> [ ] <nav>- [Pub/Sub 是什麼？](https://blog.cloud-ace.tw/big-data/pub-sub-introduction/#PubSub_%E6%98%AF%E4%BB%80%E9%BA%BC%EF%BC%9F "Pub/Sub 是什麼？")
    - [概述與優勢介紹](https://blog.cloud-ace.tw/big-data/pub-sub-introduction/#%E6%A6%82%E8%BF%B0%E8%88%87%E5%84%AA%E5%8B%A2%E4%BB%8B%E7%B4%B9 "概述與優勢介紹")
    - [6 大常見名詞](https://blog.cloud-ace.tw/big-data/pub-sub-introduction/#6_%E5%A4%A7%E5%B8%B8%E8%A6%8B%E5%90%8D%E8%A9%9E "6 大常見名詞")
    - [訊息生命週期](https://blog.cloud-ace.tw/big-data/pub-sub-introduction/#%E8%A8%8A%E6%81%AF%E7%94%9F%E5%91%BD%E9%80%B1%E6%9C%9F "訊息生命週期")
- [ack / Retry 說明](https://blog.cloud-ace.tw/big-data/pub-sub-introduction/#ack_Retry_%E8%AA%AA%E6%98%8E "ack / Retry 說明")
    - [ack](https://blog.cloud-ace.tw/big-data/pub-sub-introduction/#ack "ack")
    - [Retry ](https://blog.cloud-ace.tw/big-data/pub-sub-introduction/#Retry "Retry ")
- [常見問題](https://blog.cloud-ace.tw/big-data/pub-sub-introduction/#%E5%B8%B8%E8%A6%8B%E5%95%8F%E9%A1%8C "常見問題")
- [結論](https://blog.cloud-ace.tw/big-data/pub-sub-introduction/#%E7%B5%90%E8%AB%96 "結論")

</nav></div><div data-url="https://blog.cloud-ace.tw/big-data/pub-sub-introduction/"></div><div data-elementor-id="8074" data-elementor-type="wp-post"><section class="elementor-section elementor-top-section elementor-element elementor-element-4327899a elementor-section-boxed elementor-section-height-default elementor-section-height-default" data-element_type="section" data-id="4327899a">**Google Cloud 的 Pub/Sub 可為不同的應用程式之間提供收發訊息的功能，以及每則訊息的接收狀態追蹤功能，確保任何規模的訊息皆可穩定地傳送，有效提升用戶服務的靈活與穩健性，這次就讓我們從零開始，完整解析 Pub/Sub 功能優勢、6 大常見名詞與訊息生命週期吧！**

<div><div><div data-element_type="column" data-id="49e848e"><div><div data-element_type="widget" data-id="6312c08" data-widget_type="text-editor.default"><div><div aria-hidden="true"> </div></div></div></div></div></div></div>## <span class="ez-toc-section" id="bkmrk--1"></span>**Pub/Sub 是什麼？**

### <span class="ez-toc-section" id="bkmrk--2"></span>**概述與優勢介紹**

Pub/Sub 是一種全球性訊息傳遞的服務，並具有高可用架構，可以在應用程式與服務間交換訊息。由 Publish 與 Subscribe 組成，運用了發佈及訂閱的概念讓訊息透過非同步的方式進行傳輸，只要您的服務可以處理訊息，Pub/Sub 就會持續傳送訊息，如果您 Subscription 下的所有 Subscriber 均無法處理訊息，Pub/Sub 會自動將您的訊息在默認的情況下保留 7 天 ( [該天數最高可以延長至 31 天](https://cloud.google.com/pubsub/docs/replay-overview?hl=zh-cn#message_retention) )。

另外在 Pub/Sub 中提供 3 種類型分別為：[多對一、多對多、一對多](https://cloud.google.com/pubsub/docs/pubsub-basics?hl=zh-cn#choose_a_publish_and_subscribe_pattern)，這 3 種類型可以讓使用者更靈活的處理訊息，讓服務可以高效運作。

### <span class="ez-toc-section" id="bkmrk--3"></span>**6 大常見名詞**

對 Pub/Sub 服務有初步了解之後就要針對 Pub/Sub 的服務進行更深入的探討，首先我們先來了解 Pub/Sub 的六個常見名詞及生命週期：

<div><div><div data-element_type="column" data-id="49e848e"><div><div data-element_type="widget" data-id="6312c08" data-widget_type="text-editor.default"><div>- Publisher （發布者）：創建訊息並將訊息傳送到指定主題上的服務/人。
- Subscription（訂閱）：為 Subscriber 的上游，主要負責將訊息傳給指定的 Subscriber。
- Subscriber（訂閱者）：訂閱特定主題的服務/人。
- Topic（主題）：處理 Publisher 傳送的訊息並將其儲存至 Google Cloud Storage （簡稱 GCS） 。
- Acknowledged（已確認 ）：簡稱「 ack 」主要用於確認訊息是否有被接收最少一次。
- Retry（重試）：若訊息處理失敗會進行 Retry 用來確保所有訊息都可以被處理的機制。

</div></div></div></div></div></div>### <span class="ez-toc-section" id="bkmrk--4"></span>訊息生命週期

訊息生命週期流程（1-5）說明：（1）將訊息傳到 Pub / Sub，（2）將訊息寫入儲存空間，（3）Pub / Sub 將訊息發布給該主題的所有訂閱，（4）訂閱將訊息發送到訂閱者，（5）訂閱者回覆「 ack 」確認已經收到此訊息，並將此訊息從 GCS 上刪除；至此生命週期結束。

[![pub/sub 訊息生命週期](https://storage.googleapis.com/cloudace-tw-blog/1/2024/01/%E7%94%9F%E5%91%BD%E9%80%B1%E6%9C%9F.png)](https://storage.googleapis.com/cloudace-tw-blog/1/2024/01/%E7%94%9F%E5%91%BD%E9%80%B1%E6%9C%9F.png)

截圖自：[Cloud Pub/Sub 產品頁](https://console.cloud.google.com/getting-started)  
©2023 Google

可以發現特別是在流程（5）需要回覆「 ack 」給 Pub / Sub，這是因為 Pub / Sub 的基礎架構為「 保證訂閱 」的方式設計，確保所有訊息可以被傳送一次，因此為了確保所有資料完整被 Subscriber 接收到，會特別要求 Subscriber 進行「 ack 」，若沒有即時進行「 ack 」您的訊息則會被丟入「 Retry 」中。

## <span class="ez-toc-section" id="bkmrk--5"></span>**ack / Retry 說明**

### <span class="ez-toc-section" id="bkmrk--6"></span>ack

**若訊息未 ack 有何影響**  
若訊息未 ack 在 Pub / Sub 的設計上就會進行 Retry requests 的操作，這個動作會將未在時間內「 ack 」的訊息重新排序，這可能會導致您的 Subscriber 無法即時找到相關的訊息，進而導致該訊息延遲的狀況發生。

**如何確認「 未 ack 」的訊息**  
可以透過「 [subscription/delivery\_latency\_health\_score](https://cloud.google.com/monitoring/api/metrics_gcp#pubsub/subscription/delivery_latency_health_score) 」、「 [subscription/ack\_latencies](https://cloud.google.com/monitoring/api/metrics_gcp#pubsub/subscription/ack_latencies) 」這兩個 Metric 來確認，其中「 subscription/delivery\_latency\_health\_score 」若有數值為「 0 」代表該 Topic 過去 10 分鐘內可能有[發生未正常處理的情況](https://cloud.google.com/pubsub/docs/monitoring?hl=zh-cn#delivery_latency_health)，比如：太晚進行「 ack 」、過去 10 分鐘內有 0.1% 的確認延遲時間超過 30 秒 … 等；另外也可以從下圖右下角的「 subscription/ack\_latencies 」看出延遲的時間。

[![0110-2_pub/sub_確認 “未 ack” 的訊息](https://storage.googleapis.com/cloudace-tw-blog/1/2024/01/%E7%A2%BA%E8%AA%8D-%E6%9C%AA-ack-%E7%9A%84%E8%A8%8A%E6%81%AF.png)](https://storage.googleapis.com/cloudace-tw-blog/1/2024/01/%E7%A2%BA%E8%AA%8D-%E6%9C%AA-ack-%E7%9A%84%E8%A8%8A%E6%81%AF.png)

截圖自：[Cloud Pub/Sub 產品頁](https://console.cloud.google.com/getting-started)  
©2023 Google

### <span class="ez-toc-section" id="bkmrk--7"></span>Retry 

**甚麼原因會出現 Retry？**  
Retry 是因為 Subscriber 未在時間內進行「 ack 」，這些未被「 ack 」的訊息會被 Pub / Sub 視為 Subscriber [無法處理訊息](https://cloud.google.com/pubsub/docs/handling-failures?hl=zh-cn#:~:text=%E5%A6%82%E6%9E%9C%20Pub/Sub%20%E5%B0%9D%E8%AF%95%E4%BC%A0%E9%80%81%E6%B6%88%E6%81%AF%EF%BC%8C%E4%BD%86%E8%AE%A2%E9%98%85%E8%80%85%E6%97%A0%E6%B3%95%E7%A1%AE%E8%AE%A4%E6%B6%88%E6%81%AF%EF%BC%8C%E5%88%99%20Pub/Sub%20%E4%BC%9A%E8%87%AA%E5%8A%A8%E5%B0%9D%E8%AF%95%E9%87%8D%E6%96%B0%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF%E3%80%82%E8%BF%99%E7%A7%8D%E9%87%8D%E6%96%B0%E6%8F%90%E4%BA%A4%E5%B0%9D%E8%AF%95%E7%A7%B0%E4%B8%BA%E8%AE%A2%E9%98%85%E9%87%8D%E8%AF%95%E6%94%BF%E7%AD%96%E3%80%82%E6%82%A8%E6%97%A0%E6%B3%95%E5%90%AF%E7%94%A8%E6%88%96%E5%81%9C%E7%94%A8%E6%AD%A4%E5%8A%9F%E8%83%BD%E3%80%82)，且基於 Pub / Sub 為保證訂閱的服務，因此會將這類的訊息進行 Retry；若所有 Subscriber 均無法處理訊息，Pub/Sub 會將訊息保存至 GCS 中。  
 **3 方法避免訊息被 Retry**  
如果服務處理速度跟不上 Pub / Sub 傳送的速度您可以透過以下方式進行修改：

<div><div><div data-element_type="column" data-id="49e848e"><div><div data-element_type="widget" data-id="6312c08" data-widget_type="text-editor.default"><div>- 提升服務的機器規格，讓您的服務可以即時處理 Pub / Sub 傳送的訊息。
- 可以透過延長「 ack 」時間來延後訊息被 Pub / Sub 視為無法處理。
- 可以加入[指數退避](https://cloud.google.com/pubsub/docs/handling-failures?hl=zh-cn#exponential_backoff)的方式讓訊息在第一次失敗後等待最短指數退避的時間並重新傳送。

</div></div></div></div></div></div>以上方法都可以盡量避免訊息被 Pub / Sub Retry，可以依照需求選擇最適合的方式。

## <span class="ez-toc-section" id="bkmrk--8"></span>常見問題

**若訊息被 Retry 如何減緩影響？**  
如果您的訊息是擁有順序性的話，您可以為在[訊息加入排序](https://cloud.google.com/pubsub/docs/ordering?hl=zh-cn)，可以讓來不及處理的訊息依照第一次的順序進行排序，可以避免您的服務請求順序出問題。

**如何延長 ack 時間？**  
預設的「 ack 」時間為 10 秒，若要延長可以透過修改 [modifyackDeadline](https://cloud.google.com/pubsub/docs/lease-management?hl=zh-cn#python:~:text=%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%BA%93%E5%8F%AF%E4%BB%A5%E9%80%9A%E8%BF%87%E5%AE%9A%E6%9C%9F%E5%8F%91%E5%87%BA%20modifyAckDeadline%20%E8%AF%B7%E6%B1%82) 來延長「 ack 」的時間，最高可將「 ack 」時間延長至 60 分鐘。

**若特定時間出現龐大流量該怎麼應對？**  
在 Pub / Sub 中提供使用 [Flow Control](https://cloud.google.com/pubsub/docs/flow-control?hl=zh-cn) 的方式針對瞬時流量進行處理。

## <span class="ez-toc-section" id="bkmrk--9"></span>結論

在設計服務流程時訊息處理是相當重要的一環，傳統地端可能需要自行架設、維護 Kafka、RabbitMQ… 這類型的服務費時又麻煩，不過 Google Cloud 提供 Pub/Sub 服務可以使用，並結合了保證訂閱、多對多、延時處理… 等優點，讓使用者可以減少管理訊息相關的時間，更專注在開發、優化服務上。

**這次介紹了 Pub/Sub 功能優勢， 6 大常見名詞與訊息生命週期。各位若有進一步的 Pub/Sub 操作疑問，歡迎[聯絡 Cloud Ace ](https://tw.cloud-ace.com/contact/)獲得更進一步的資訊。**

<div><div data-element_type="column" data-id="49e848e"><div><div data-element_type="widget" data-id="6312c08" data-widget_type="text-editor.default"><div><div aria-hidden="true"> </div></div></div></div></div></div></section></div></div>

# 【CCP】Google Cloud Platform 服務總覽

出處：[https://ithelp.ithome.com.tw/users/20151036/ironman/6131](https://ithelp.ithome.com.tw/users/20151036/ironman/6131)

## Region 和 Zone 的差別

---

在 Google Cloud 裡面，使用了 Region 和 Zone 的概念，分別去區分不同地理區塊的服務

[![image-1745571406812.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-04/scaled-1680-/LNRbcR2k76xjHRYB-image-1745571406812.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-04/LNRbcR2k76xjHRYB-image-1745571406812.png)

## 管理 Google Cloud 的四種方法

---

介紹完了 Region 和 Zone 的差別，接著介紹一下管理 Google Cloud 中的服務的四種方法

[![image-1745571435771.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-04/scaled-1680-/8FmNWw7mf2xWecb4-image-1745571435771.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-04/8FmNWw7mf2xWecb4-image-1745571435771.png)

<table class="min-w-full" data-end="2470" data-start="1946" id="bkmrk-%E5%B7%A5%E5%85%B7-%E4%BB%8B%E9%9D%A2%E9%A1%9E%E5%9E%8B-%E5%8A%9F%E8%83%BD%E5%BC%B7%E5%BA%A6-%E4%BD%BF%E7%94%A8%E6%99%82%E6%A9%9F-go"><thead data-end="2021" data-start="1946"><tr data-end="2021" data-start="1946"><th data-end="1966" data-start="1946">工具</th><th data-end="1977" data-start="1966">介面類型</th><th data-end="1987" data-start="1977">功能強度</th><th data-end="2021" data-start="1987">使用時機</th></tr></thead><tbody data-end="2470" data-start="2112"><tr data-end="2187" data-start="2112"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2134" data-start="2112">Google Cloud Console</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2147" data-start="2134">圖形 UI</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2159" data-start="2147">中等</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2187" data-start="2159">建立資源、設定、報表</td></tr><tr data-end="2258" data-start="2188"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2210" data-start="2188">gcloud SDK</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2220" data-start="2210">命令列工具</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2232" data-start="2220">最強</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2258" data-start="2232">自動化部署、開發整合、CI/CD</td></tr><tr data-end="2326" data-start="2259"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2281" data-start="2259">Cloud Shell</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2291" data-start="2281">雲端終端機</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2304" data-start="2291">高</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2326" data-start="2304">快速部署/測試指令，不佔本地資源</td></tr><tr data-end="2398" data-start="2327"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2349" data-start="2327">Cloud APIs</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2360" data-start="2349">程式介面</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2373" data-start="2360">高</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2398" data-start="2373">系統對接、自動化服務整合</td></tr><tr data-end="2470" data-start="2399"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2421" data-start="2399">Cloud Mobile App</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2434" data-start="2421">手機 App</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2446" data-start="2434">輕量</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2470" data-start="2446">查看狀態、接收通知、緊急操作</td></tr></tbody></table>

## Google Cloud 中 Project 結構

---

下面這張圖描述了 Google Cloud 中的 Project 結構，不過這張圖要從最下層往上看

[![image-1745571478114.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-04/scaled-1680-/Duhg7HbzxKXSke42-image-1745571478114.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-04/Duhg7HbzxKXSke42-image-1745571478114.png)

## Google Cloud 中的運算服務

---

- Compute Engine
- App Engine
- Cloud Functions
- Google Kubernetes Engine
- Cloud Run

[![image-1745570618413.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-04/scaled-1680-/5hQcY3xZPcZ3BS3H-image-1745570618413.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-04/5hQcY3xZPcZ3BS3H-image-1745570618413.png)

### **Google Cloud Run、App Engine、Cloud Functions** 三者的完整比較：

---

#### 🔍 三種 Serverless 服務比較表

<div id="bkmrk-%E9%A0%85%E7%9B%AE-cloud-run-app-eng"><button class="hover:bg-token-main-surface-secondary text-token-text-secondary pointer-events-auto rounded-lg px-1 py-1 opacity-0 transition-opacity duration-200 group-focus-within:opacity-100 group-hover:opacity-100"><svg class="icon-md-heavy" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg></button><div><table class="min-w-full" data-end="1947" data-start="160"><thead data-end="316" data-start="160"><tr data-end="316" data-start="160"><th data-end="185" data-start="160">項目</th><th data-end="229" data-start="185">**Cloud Run**</th><th data-end="271" data-start="229">**App Engine**</th><th data-end="316" data-start="271">**Cloud Functions**</th></tr></thead><tbody data-end="1947" data-start="478"><tr data-end="616" data-start="478"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="501" data-start="478">🧩 適用層級</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="539" data-start="501">容器（Container）層級</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="576" data-start="539">應用（Application）層級</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="616" data-start="576">函數（Function）層級</td></tr><tr data-end="757" data-start="617"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="640" data-start="617">🚀 啟動方式</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="680" data-start="640">HTTP 請求觸發</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="719" data-start="680">HTTP、排程、Task Queue</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="757" data-start="719">HTTP、事件觸發（如 Storage、PubSub）</td></tr><tr data-end="881" data-start="758"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="782" data-start="758">🛠️ 開發模式</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="811" data-start="782">自行打包容器，任何語言框架皆可</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="847" data-start="811">GCP 支援語言（Java, Python, Go, Node）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="881" data-start="847">限制在特定語言框架（Node, Python, Go 等）</td></tr><tr data-end="1000" data-start="882"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="905" data-start="882">⚙️ 彈性調整</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="944" data-start="905">✅ 自動擴展至零</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="971" data-start="944">✅ 自動擴展（可設定最小/最大執行數）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1000" data-start="971">✅ 自動擴展至零（每次執行為全新環境）</td></tr><tr data-end="1131" data-start="1001"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1022" data-start="1001">🐳 是否支援容器</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1059" data-start="1022">✅ 可自定義容器</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1093" data-start="1059">❌（App Engine Flex 支援，但較複雜）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1131" data-start="1093">❌（只支援函數級）</td></tr><tr data-end="1260" data-start="1132"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1155" data-start="1132">🧾 計費方式</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1191" data-start="1155">請求數 + CPU + 記憶體 + 時間</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1225" data-start="1191">請求數 + 時間 + 記憶體</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1260" data-start="1225">函數呼叫次數 + 執行時間 + 資源</td></tr><tr data-end="1379" data-start="1261"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1285" data-start="1261">🧠 冷啟動</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1317" data-start="1285">較低，若設定最小實例可避免</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1346" data-start="1317">標準環境會冷啟，高級環境可控制</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1379" data-start="1346">冷啟明顯（尤其低頻率觸發）</td></tr><tr data-end="1522" data-start="1380"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1403" data-start="1380">🔄 狀態保持</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1440" data-start="1403">不可保留（Stateless）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1479" data-start="1440">不可保留</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1522" data-start="1479">不可保留</td></tr><tr data-end="1654" data-start="1523"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1546" data-start="1523">📦 部署單位</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1579" data-start="1546">整個容器（任意語言）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1616" data-start="1579">應用程式檔案 + config.yaml</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1654" data-start="1616">單一函數檔案（Function）</td></tr><tr data-end="1796" data-start="1655"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1676" data-start="1655">🌐 自訂網域 / HTTPS 支援</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1710" data-start="1676">✅ 可設定自訂網域與 HTTPS</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1751" data-start="1710">✅</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1796" data-start="1751">✅</td></tr><tr data-end="1947" data-start="1797"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1820" data-start="1797">🔐 IAM 權限控管</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1860" data-start="1820">✅ 精細控制</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1902" data-start="1860">✅</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1947" data-start="1902">✅</td></tr></tbody></table>

</div></div>---

#### 🧠 簡易選擇建議

<div id="bkmrk-%E4%BD%A0%E8%A6%81%E7%9A%84%E6%98%AF%E2%80%A6-%E5%BB%BA%E8%AD%B0%E4%BD%BF%E7%94%A8-%E5%8F%AF%E4%BB%A5%E8%87%AA%E7%94%B1%E4%BD%BF%E7%94%A8%E4%BB%BB%E4%BD%95%E8%AA%9E"><button class="hover:bg-token-main-surface-secondary text-token-text-secondary pointer-events-auto rounded-lg px-1 py-1 opacity-0 transition-opacity duration-200 group-focus-within:opacity-100 group-hover:opacity-100"><svg class="icon-md-heavy" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg></button><div><table class="min-w-full" data-end="2453" data-start="1969"><thead data-end="2033" data-start="1969"><tr data-end="2033" data-start="1969"><th data-end="2015" data-start="1969">你要的是…</th><th data-end="2033" data-start="2015">建議使用</th></tr></thead><tbody data-end="2453" data-start="2106"><tr data-end="2164" data-start="2106"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2142" data-start="2106">可以自由使用任何語言與架構</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2164" data-start="2142">✅ Cloud Run</td></tr><tr data-end="2222" data-start="2165"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2200" data-start="2165">最少設定、支援完整應用部署</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2222" data-start="2200">✅ App Engine</td></tr><tr data-end="2279" data-start="2223"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2257" data-start="2223">僅需要處理單一任務 / 事件驅動功能</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2279" data-start="2257">✅ Cloud Functions</td></tr><tr data-end="2339" data-start="2280"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2317" data-start="2280">想用 Docker 容器、可相容本地環境</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2339" data-start="2317">✅ Cloud Run</td></tr><tr data-end="2398" data-start="2340"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2376" data-start="2340">想部署 Flask、Express 但不想處理容器細節</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2398" data-start="2376">✅ App Engine</td></tr><tr data-end="2453" data-start="2399"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2431" data-start="2399">想做圖片上傳通知、排程簡訊等小任務</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2453" data-start="2431">✅ Cloud Functions</td></tr></tbody></table>

</div></div>---

#### 🔧 實際開發場景舉例：

<div id="bkmrk-%E4%BD%BF%E7%94%A8%E6%83%85%E5%A2%83-%E9%81%A9%E5%90%88%E6%9C%8D%E5%8B%99-%E9%83%A8%E7%BD%B2-spring-"><button class="hover:bg-token-main-surface-secondary text-token-text-secondary pointer-events-auto rounded-lg px-1 py-1 opacity-0 transition-opacity duration-200 group-focus-within:opacity-100 group-hover:opacity-100"><svg class="icon-md-heavy" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg></button><div><table class="min-w-full" data-end="2995" data-start="2478"><thead data-end="2551" data-start="2478"><tr data-end="2551" data-start="2478"><th data-end="2531" data-start="2478">使用情境</th><th data-end="2551" data-start="2531">適合服務</th></tr></thead><tbody data-end="2995" data-start="2634"><tr data-end="2705" data-start="2634"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2681" data-start="2634">部署 Spring Boot 應用，並自定義 Dockerfile</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2705" data-start="2681">Cloud Run</td></tr><tr data-end="2778" data-start="2706"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2754" data-start="2706">部署 Python Web（Flask）快速上線</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2778" data-start="2754">App Engine</td></tr><tr data-end="2849" data-start="2779"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2825" data-start="2779">使用 GCS 上傳觸發縮圖處理</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2849" data-start="2825">Cloud Functions</td></tr><tr data-end="2923" data-start="2850"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)] min-w-[calc(var(--thread-content-max-width)/3)]" data-end="2899" data-start="2850">開發 RESTful API，架構使用 FastAPI + Redis + Gunicorn</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2923" data-start="2899">Cloud Run</td></tr><tr data-end="2995" data-start="2924"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2973" data-start="2924">設定每日 08:00 發送報表</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2995" data-start="2973">Cloud Functions（排程）</td></tr></tbody></table>

</div></div>## Google Cloud 中的數據儲存服務

---

Google Cloud 支援五種數據的儲存服務，而這五種服務，又可以分成三大類，分別是「一般檔案的儲存服務」、「SQL 的儲存服務」以及「NoSQL 的儲存服務」

[![image-1745570710643.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-04/scaled-1680-/W7YOPBbL35WdCfNe-image-1745570710643.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-04/W7YOPBbL35WdCfNe-image-1745570710643.png)

### 數據儲存服務總結

[![image-1745568492002.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-04/scaled-1680-/kqCE38zooyzxU3F7-image-1745568492002.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-04/kqCE38zooyzxU3F7-image-1745568492002.png)

### Bigtable 與 Bigquery 比較

#### 🔍 基本說明

<div id="bkmrk-%E5%90%8D%E7%A8%B1-%E8%AA%AA%E6%98%8E-bigtable-%E5%88%86%E6%95%A3%E5%BC%8F-n"><button class="hover:bg-token-main-surface-secondary text-token-text-secondary pointer-events-auto rounded-lg px-1 py-1 opacity-0 transition-opacity duration-200 group-focus-within:opacity-100 group-hover:opacity-100"><svg class="icon-md-heavy" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg></button><div><table class="min-w-full" data-end="426" data-start="127"><thead data-end="210" data-start="127"><tr data-end="210" data-start="127"><th data-end="140" data-start="127">名稱</th><th data-end="210" data-start="140">說明</th></tr></thead><tbody data-end="426" data-start="299"><tr data-end="361" data-start="299"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="314" data-start="299">**Bigtable**</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="361" data-start="314">分散式 NoSQL 資料庫，適合高頻率讀寫、低延遲的應用（類似 HBase）</td></tr><tr data-end="426" data-start="362"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="377" data-start="362">**BigQuery**</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)] min-w-[calc(var(--thread-content-max-width)/3)]" data-end="426" data-start="377">分析型資料倉儲，專門處理超大資料集的查詢與分析（類似 Redshift、Snowflake）</td></tr></tbody></table>

</div></div>---

#### 🧠 詳細比較表

<div id="bkmrk-%E9%A0%85%E7%9B%AE-bigtable-bigquery"><button class="hover:bg-token-main-surface-secondary text-token-text-secondary pointer-events-auto rounded-lg px-1 py-1 opacity-0 transition-opacity duration-200 group-focus-within:opacity-100 group-hover:opacity-100"><svg class="icon-md-heavy" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg></button><div><table class="min-w-full" data-end="1942" data-start="446"><thead data-end="579" data-start="446"><tr data-end="579" data-start="446"><th data-end="464" data-start="446">項目</th><th data-end="521" data-start="464">**Bigtable**</th><th data-end="579" data-start="521">**BigQuery**</th></tr></thead><tbody data-end="1942" data-start="716"><tr data-end="839" data-start="716"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="732" data-start="716">📦 資料類型</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="788" data-start="732">NoSQL，Key-Value / Wide-column store</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="839" data-start="788">SQL-based，結構化資料表</td></tr><tr data-end="936" data-start="840"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="856" data-start="840">🧩 適用場景</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="893" data-start="856">即時讀寫、大量設備資料、時序資料（IoT、日誌）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="936" data-start="893">大數據分析、商業智慧報表、ETL 處理</td></tr><tr data-end="1064" data-start="937"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="954" data-start="937">🛠️ 查詢方式</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1011" data-start="954">API / gRPC / HBase client</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1064" data-start="1011">SQL（支援標準 SQL）</td></tr><tr data-end="1176" data-start="1065"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1080" data-start="1065">🔁 查詢靈活度</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1127" data-start="1080">限制多（只能依 row key 查詢）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1176" data-start="1127">高（可聚合、JOIN、Window Function）</td></tr><tr data-end="1290" data-start="1177"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1192" data-start="1177">⚡ 效能焦點</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1241" data-start="1192">**寫入快、讀取延遲低**</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1290" data-start="1241">**掃描大量資料效率高**</td></tr><tr data-end="1397" data-start="1291"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1307" data-start="1291">💾 儲存設計</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1354" data-start="1307">儲存在 Colossus + SSTable（HBase-like 分散式設計）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1397" data-start="1354">儲存在 Dremel 架構（專為資料分析優化）</td></tr><tr data-end="1509" data-start="1398"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1415" data-start="1398">🗃️ 索引支援</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1460" data-start="1415">❌ 沒有二級索引，僅支援 row key 前綴</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1509" data-start="1460">✅ 支援複雜查詢與分析</td></tr><tr data-end="1624" data-start="1510"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1525" data-start="1510">🔢 每秒處理量</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1575" data-start="1525">百萬級 TPS（高併發）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1624" data-start="1575">適合大批量（TB~PB）分析</td></tr><tr data-end="1748" data-start="1625"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1640" data-start="1625">⏳ 查詢延遲</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1694" data-start="1640">毫秒等級</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1748" data-start="1694">秒 ~ 分鐘等級</td></tr><tr data-end="1846" data-start="1749"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1765" data-start="1749">💰 計費方式</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1806" data-start="1765">根據儲存容量 + 節點數（可橫向擴充）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1846" data-start="1806">根據掃描的資料量計價（可選定價模式）</td></tr><tr data-end="1942" data-start="1847"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1863" data-start="1847">🔧 管理需求</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1904" data-start="1863">類似資料庫（需設計 schema、選 row key、規劃節點）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1942" data-start="1904">類似報表工具（丟資料就能查，幾乎零維運）</td></tr></tbody></table>

</div></div>---

#### 📌 使用情境建議

<div id="bkmrk-%E9%9C%80%E6%B1%82%2F%E6%83%85%E5%A2%83-%E5%BB%BA%E8%AD%B0%E4%BD%BF%E7%94%A8-iot-%E8%A3%9D%E7%BD%AE%E6%AF%8F%E7%A7%92%E5%9B%9E"><button class="hover:bg-token-main-surface-secondary text-token-text-secondary pointer-events-auto rounded-lg px-1 py-1 opacity-0 transition-opacity duration-200 group-focus-within:opacity-100 group-hover:opacity-100"><svg class="icon-md-heavy" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg></button><div><table class="min-w-full" data-end="2349" data-start="1963"><thead data-end="2012" data-start="1963"><tr data-end="2012" data-start="1963"><th data-end="1995" data-start="1963">需求/情境</th><th data-end="2012" data-start="1995">建議使用</th></tr></thead><tbody data-end="2349" data-start="2070"><tr data-end="2116" data-start="2070"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2095" data-start="2070">IoT 裝置每秒回傳大量資料</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2116" data-start="2095">✅ Bigtable</td></tr><tr data-end="2159" data-start="2117"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2138" data-start="2117">要統計每月業績報表、跑分析 SQL</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2159" data-start="2138">✅ BigQuery</td></tr><tr data-end="2203" data-start="2160"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2182" data-start="2160">需要實時查詢某一筆用戶記錄</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2203" data-start="2182">✅ Bigtable</td></tr><tr data-end="2258" data-start="2204"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2237" data-start="2204">想跑 SELECT count, avg, group by</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2258" data-start="2237">✅ BigQuery</td></tr><tr data-end="2307" data-start="2259"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2291" data-start="2259">需要和 Data Studio 或 Looker 整合報表</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2307" data-start="2291">✅ BigQuery</td></tr><tr data-end="2349" data-start="2308"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2328" data-start="2308">想儲存百億筆資料但需低延遲查詢</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2349" data-start="2328">✅ Bigtable</td></tr></tbody></table>

</div></div>---

#### 🔁 搭配使用範例

很多系統會這樣搭配：

1. **Bigtable：實時寫入**
    
    
    - 寫入裝置或行為記錄（如瀏覽行為、IoT 資料）
2. **定期匯入到 BigQuery**
    
    
    - 跑 ETL，進行分析與報表產出

---

## Google Cloud 中的 API 管理服務

---

影片中有提到三種 Google Cloud 中常見的 API 管理服務，分別是：

- Apigee API Management
- API Gateway
- Cloud Endpoints

### 🔍 快速對比表：Apigee vs API Gateway vs Cloud Endpoints

<div id="bkmrk-%E9%A0%85%E7%9B%AE-apigee-api-gatewa"><button class="hover:bg-token-main-surface-secondary text-token-text-secondary pointer-events-auto rounded-lg px-1 py-1 opacity-0 transition-opacity duration-200 group-focus-within:opacity-100 group-hover:opacity-100"><svg class="icon-md-heavy" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg></button><div><table class="min-w-full" data-end="2348" data-start="121"><thead data-end="311" data-start="121"><tr data-end="311" data-start="121"><th data-end="147" data-start="121">項目</th><th data-end="201" data-start="147">**Apigee**</th><th data-end="256" data-start="201">**API Gateway**</th><th data-end="311" data-start="256">**Cloud Endpoints**</th></tr></thead><tbody data-end="2348" data-start="506"><tr data-end="671" data-start="506"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="532" data-start="506">🧠 定位</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="578" data-start="532">企業級 API 管理平台</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="624" data-start="578">簡化、輕量的 GCP 原生 API Gateway</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="671" data-start="624">開發者導向的輕量級 API Proxy</td></tr><tr data-end="848" data-start="672"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="697" data-start="672">🛠️ 建立方式</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="746" data-start="697">使用 Apigee UI 建立 API Proxy</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="799" data-start="746">部署 `api-gateway.yaml` + GCP service</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)] min-w-[calc(var(--thread-content-max-width)/3)]" data-end="848" data-start="799">使用 ESP（Extensible Service Proxy）與 OpenAPI 配置</td></tr><tr data-end="1003" data-start="849"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="872" data-start="849">📊 功能完整性</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="904" data-start="872">✅ 完整 API 生命週期管理（分析、流量管控、金鑰等）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="953" data-start="904">⚠️ 僅基本 API Proxy 與驗證</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1003" data-start="953">⚠️ 基本功能 + Firebase / Auth0 整合</td></tr><tr data-end="1170" data-start="1004"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1027" data-start="1004">📈 分析與報表</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1071" data-start="1027">✅ 內建精細分析報表</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1119" data-start="1071">❌ 無分析功能</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1170" data-start="1119">⚠️ 可搭配 Stackdriver/Cloud Logging</td></tr><tr data-end="1352" data-start="1171"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1192" data-start="1171">🔐 驗證/授權支援</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1242" data-start="1192">✅ OAuth2, API Key, JWT, SAML, 多種認證</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1297" data-start="1242">✅ JWT / API Key / IAM</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1352" data-start="1297">✅ JWT / API Key / Firebase</td></tr><tr data-end="1521" data-start="1353"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1377" data-start="1353">⚙️ API 開發方式</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1420" data-start="1377">提供 GUI、Flow 編輯器、邏輯處理</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1469" data-start="1420">使用 OpenAPI 定義 + 設定 YAML</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1521" data-start="1469">OpenAPI / gRPC 定義（ESP Proxy）</td></tr><tr data-end="1683" data-start="1522"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1548" data-start="1522">🔁 效能</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1594" data-start="1548">高（企業級 SLA）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1640" data-start="1594">中等，適合輕量流量</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1683" data-start="1640">輕量，設計給內部/開發測試用</td></tr><tr data-end="1835" data-start="1684"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1708" data-start="1684">💲 收費模式</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1745" data-start="1708">依照 API 呼叫數、功能計費（**需額外購買**）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1788" data-start="1745">依流量計費（依照 GCP 定價）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1835" data-start="1788">依照 ESP Proxy 的 GKE/GCE/Cloud Run 運行費用計費</td></tr><tr data-end="2020" data-start="1836"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1861" data-start="1836">🏗️ 運行平台</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1909" data-start="1861">Apigee X（GCP原生）、Hybrid</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1964" data-start="1909">Cloud Run / GKE / GCE</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2020" data-start="1964">GKE / Cloud Run / App Engine</td></tr><tr data-end="2183" data-start="2021"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2037" data-start="2021">🧩 複雜功能（限流、轉換）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2079" data-start="2037">✅ 支援（flow policy 可自定義 JSON/XML 轉換等）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2130" data-start="2079">❌ 不支援</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2183" data-start="2130">❌ 不支援</td></tr><tr data-end="2348" data-start="2184"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2207" data-start="2184">🔧 部署簡易性</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2248" data-start="2207">中等（企業級 GUI + 設定需學習）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2299" data-start="2248">高（YAML + CLI）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2348" data-start="2299">中等（需熟 ESP/OpenAPI）</td></tr></tbody></table>

</div></div>---

### 🔑 應用場景建議

<div id="bkmrk-%E9%9C%80%E6%B1%82-%2F-%E7%8B%80%E6%B3%81-%E5%BB%BA%E8%AD%B0%E4%BD%BF%E7%94%A8-%E4%BC%81%E6%A5%AD%E9%9C%80%E8%A6%81%E5%AE%8C%E6%95%B4-"><button class="hover:bg-token-main-surface-secondary text-token-text-secondary pointer-events-auto rounded-lg px-1 py-1 opacity-0 transition-opacity duration-200 group-focus-within:opacity-100 group-hover:opacity-100"><svg class="icon-md-heavy" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg></button><div><table class="min-w-full" data-end="2851" data-start="2369"><thead data-end="2430" data-start="2369"><tr data-end="2430" data-start="2369"><th data-end="2409" data-start="2369">需求 / 狀況</th><th data-end="2430" data-start="2409">建議使用</th></tr></thead><tbody data-end="2851" data-start="2500"><tr data-end="2550" data-start="2500"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2525" data-start="2500">企業需要完整 API 管理功能（流控、報表等）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2550" data-start="2525">✅ Apigee</td></tr><tr data-end="2611" data-start="2551"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2586" data-start="2551">GCP 環境輕量部署 + IAM 驗證 API</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2611" data-start="2586">✅ API Gateway</td></tr><tr data-end="2666" data-start="2612"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2641" data-start="2612">開發階段/原型開發 + 自訂認證方式</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2666" data-start="2641">✅ Cloud Endpoints</td></tr><tr data-end="2729" data-start="2667"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2704" data-start="2667">需要整合 Firebase 認證</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2729" data-start="2704">✅ Cloud Endpoints</td></tr><tr data-end="2789" data-start="2730"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2769" data-start="2730">多租戶 API 管理、API 商業化（API Monetization）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2789" data-start="2769">✅ Apigee</td></tr><tr data-end="2851" data-start="2790"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2826" data-start="2790">想快速部署在 Cloud Run + IAM 控制</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2851" data-start="2826">✅ API Gateway</td></tr></tbody></table>

</div></div>---

### 🧪 技術選擇心法：

- 👉 **選 Apigee** 如果你是企業客戶、要做 API 授權金鑰控管、頻率限制、報表、開發者入口網站（dev portal）等。
- 👉 **選 API Gateway** 如果你只是想快速幫 Cloud Run 上的 API 加上 HTTPS、安全性。
- 👉 **選 Cloud Endpoints** 如果你熟 OpenAPI、只想讓 API 有認證與基本日誌記錄，不需要 GUI。

## 權限管理服務

[![image-1745570380775.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-04/scaled-1680-/AO0cbHa2oSgmA89U-image-1745570380775.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-04/AO0cbHa2oSgmA89U-image-1745570380775.png)

[![image-1745570465944.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-04/scaled-1680-/RHUpIAWQiTnaZvkf-image-1745570465944.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-04/RHUpIAWQiTnaZvkf-image-1745570465944.png)

---

## 說明 Iaas Paas Saas Faas

這四個名詞是雲端服務的不同層級與服務模型，分別為：

---

### 🏗️ IaaS（Infrastructure as a Service）基礎設施即服務

- **提供內容：** 虛擬機（VM）、儲存空間、網路、作業系統等基礎資源
- **使用者負責：** 自行安裝應用程式、中介軟體、設定安全性等
- **彈性高，自由度大**
- **常見例子：**
    
    
    - AWS EC2
    - Google Compute Engine
    - Microsoft Azure VM

> ✅ 適用於：需要完整控制權限的系統管理員與 DevOps 團隊

---

### 🧱 PaaS（Platform as a Service）平台即服務

- **提供內容：** 開發平台（含OS、Runtime、DB、Framework）
- **使用者負責：** 上傳應用程式，開發、部署、維護應用邏輯
- **省去基礎建設與系統維護負擔**
- **常見例子：**
    
    
    - Google App Engine
    - Heroku
    - AWS Elastic Beanstalk

> ✅ 適用於：開發人員專注寫程式，不想管底層架構

---

### 📦 SaaS（Software as a Service）軟體即服務

- **提供內容：** 完整的軟體應用，通常透過瀏覽器存取
- **使用者負責：** 只需要使用，不用管安裝、更新、維運
- **完全託管、快速上手**
- **常見例子：**
    
    
    - Gmail / Outlook
    - Google Docs / Office 365
    - Salesforce
    - Dropbox

> ✅ 適用於：終端使用者或企業快速取得現成解決方案

---

### ⚡ FaaS（Function as a Service）函數即服務

- **提供內容：** 運行特定函數或任務的執行環境
- **使用者負責：** 撰寫函數邏輯，其他都交給雲端處理（包含資源擴展）
- **屬於 Serverless 架構的一種**
- **常見例子：**
    
    
    - AWS Lambda
    - Google Cloud Functions
    - Azure Functions

> ✅ 適用於：事件驅動的任務（如：圖檔轉換、資料處理、API 回應等）

---

### 對照圖表總覽：

<div id="bkmrk-%E5%B1%A4%E7%B4%9A-%E4%BD%A0%E8%B2%A0%E8%B2%AC%E7%9A%84%E9%83%A8%E5%88%86-%E4%BE%8B%E5%AD%90-iaas-ap"><button class="hover:bg-token-main-surface-secondary text-token-text-secondary pointer-events-auto rounded-lg px-1 py-1 opacity-0 transition-opacity duration-200 group-focus-within:opacity-100 group-hover:opacity-100"><svg class="icon-md-heavy" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg></button><div><table class="min-w-full" data-end="1452" data-start="1028"><thead data-end="1091" data-start="1028"><tr data-end="1091" data-start="1028"><th data-end="1041" data-start="1028">層級</th><th data-end="1066" data-start="1041">你負責的部分</th><th data-end="1091" data-start="1066">例子</th></tr></thead><tbody data-end="1452" data-start="1166"><tr data-end="1238" data-start="1166"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1181" data-start="1166">IaaS</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1212" data-start="1181">App + OS + Middleware + DB</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1238" data-start="1212">AWS EC2、GCP VM</td></tr><tr data-end="1311" data-start="1239"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1254" data-start="1239">PaaS</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1285" data-start="1254">App + DB</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1311" data-start="1285">Heroku、GCP App Engine</td></tr><tr data-end="1381" data-start="1312"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1327" data-start="1312">SaaS</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1355" data-start="1327">僅使用</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1381" data-start="1355">Gmail、Google Docs</td></tr><tr data-end="1452" data-start="1382"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1397" data-start="1382">FaaS</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1426" data-start="1397">僅寫 function</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1452" data-start="1426">AWS Lambda、GCF</td></tr></tbody></table>

</div></div>說明 Iaas Paas Saas Faas

# PowerDNS

# 設定頁說明

# Create Zone

[![image-1740038890928.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-02/scaled-1680-/xjF4xDlX85eU9JJZ-image-1740038890928.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-02/xjF4xDlX85eU9JJZ-image-1740038890928.png)

---

### 區域名稱 (Zone Name)

請輸入您的區域名稱，格式為 `名稱.頂級域`（例如 `powerdns-admin.com`）。您也可以輸入子區域來建立委派區域（例如 `sub.powerdns-admin.com`），這對於將控制權委派給其他使用者非常有用。

### 區域覆蓋記錄 (Zone Override Record)

啟用後，即使使用者沒有管理父區域的權限，也能繞過驗證錯誤。

### 帳戶 (Account)

指定該區域所使用的 PowerDNS 帳戶資訊。

### 區域類型 (Zone Type)

區域類型決定該區域如何在多台 DNS 伺服器之間進行同步：

- **原生 (Native)**：伺服器不會執行主要（Primary）或次要（Secondary）區域的功能。
- **主要 (Primary)**：伺服器作為主要 DNS 伺服器，並會將區域資料傳輸（AXFR）至其他設定為次要的伺服器。
- **次要 (Secondary)**：伺服器作為次要 DNS 伺服器，並會從設定為主要的伺服器請求並接收區域資料傳輸（AXFR）。

### 區域範本 (Zone Template)

指定此區域最初應從哪個現有的區域範本複製設定。

### SOA-EDIT-API 設定

`SOA-EDIT-API` 設定用於決定當區域變更時，如何更新 SOA (Start of Authority) 序號：

- **DEFAULT（預設）**：產生 `YYYYMMDD01` 格式的 SOA 序號。 
    - 若當前序號小於產生的序號，則使用產生的序號。
    - 若當前序號大於或等於產生的序號，則將當前序號加 1。
- **INCREASE（遞增）**：將當前 SOA 序號加 1。
- **EPOCH（時間戳記）**：將序號設定為從 Unix 紀元（1970-01-01 00:00:00 UTC）以來的總秒數。
- **OFF（關閉）**：停用 SOA 序號的自動更新。

---

這樣的翻譯較符合台灣地區的用語習慣，如果有特定的用詞需求也可以再進一步調整！

# 【PowerDns】安裝

[https://doc.powerdns.com/recursor/getting-started.html#debian-based-distributions](https://doc.powerdns.com/recursor/getting-started.html#debian-based-distributions)

關閉 `systemd-resolved`，讓出 port 53

```shell
sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolved
```

install <span class="s1">pdns-recursor  
</span>

```shell
# 安裝 powerdns recursor
apt-get install pdns-recursor 
# 尋找版本
# pdns_recursor --version

root@dockr204:/etc/powerdns# pdns_recursor --version
Aug 08 17:34:07 PowerDNS Recursor 4.6.0 (C) 2001-2021 PowerDNS.COM BV
Aug 08 17:34:07 Using 64-bits mode. Built using gcc 11.2.0.
Aug 08 17:34:07 PowerDNS comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it according to the terms of the GPL version 2.
```

設定資料夾在 /etc/powerdns

```shell
root@dockr204:/etc/powerdns# ll /etc/powerdns
total 76
drwxr-xr-x   3 root root  4096 Aug  8 17:31 ./
drwxr-xr-x 104 root root  4096 Aug  8 17:24 ../
-rw-r--r--   1 root root 58718 Mar 12  2022 recursor.conf
drwxr-xr-x   2 root root  4096 Mar 12  2022 recursor.d/
-rw-r--r--   1 root root   272 Mar 12  2022 recursor.lua


# ll /etc/powerdns/recursor.d
total 8
drwxr-xr-x 2 root root 4096 Mar 12  2022 ./
drwxr-xr-x 3 root root 4096 Aug  8 17:31 ../


# cat recursor.lua
-- Debian default Lua configuration file for PowerDNS Recursor

-- Load DNSSEC root keys from dns-root-data package.
-- Note: If you provide your own Lua configuration file, consider
-- running rootkeys.lua too.
dofile("/usr/share/pdns-recursor/lua-config/rootkeys.lua")

# cat recursor.conf | grep -v "#" | grep -v "^$"
config-dir=/etc/powerdns
hint-file=/usr/share/dns/root.hints
include-dir=/etc/powerdns/recursor.d
local-address=127.0.0.1
lua-config-file=/etc/powerdns/recursor.lua
public-suffix-list-file=/usr/share/publicsuffix/public_suffix_list.dat
quiet=yes
security-poll-suffix=
```

- 備份預設檔，建立新的設定檔

```shell
sudo systemctl disable systemd-resolved
sudo systemctl stop systemd-resolved
```

修改 recursor.conf

```shell
api-key=AAAABBBDDDCCC
webserver=yes
webserver-port=8082
webserver-address=0.0.0.0
webserver-allow-from=0.0.0.0/0

allow-from=127.0.0.1, 192.168.0.0/24

# 查 abc.com.tw 的交給本地 Authoritative Server（UDP 54）
forward-zones=abc.com.tw=127.0.0.1:54

# 其他一律轉外部解析
forward-zones-recurse=.=8.8.8.8, .=168.95.1.1

local-address=0.0.0.0
local-port=53

setgid=pdns
setuid=pdns

logging-facility=0
max-negative-ttl=3600
quiet=yes

# 其他設定
config-dir=/etc/powerdns
hint-file=/usr/share/dns/root.hints
include-dir=/etc/powerdns/recursor.d
lua-config-file=/etc/powerdns/recursor.lua
```

- 設定自動啟動及重啟 PowerDNS-Recursor 服務

```shell
systemctl start pdns-recursor 
systemctl enable pdns-recursor
```

\# 安裝權威伺服器

```shell
# 安裝權威伺服器
sudo apt-get install pdns-server
```

- 備份預設檔，建立新的設定檔

```shell
mv /etc/powerdns/pdns.conf /etc/powerdns/pdns.conf.bak
vim /etc/powerdns/pdns.conf
```

# 【PowerDns】pdnsutil 速查表

OK！下面幫你把 **pdnsutil 常用指令** 與 **PowerDNS Authoritative REST API** 的核心用法，整理成一份實用速查表（適用你現在的 SQLite 後端設定）。指令預設在伺服器上執行，API 範例用 `curl`。

---

# pdnsutil 速查表

> 小重點：`pdnsutil` 會用 `/etc/powerdns/pdns.conf` 的後端設定（像你現在的 `gsqlite3-database=/var/lib/powerdns/pdns.db`）。一般情況**不需要**在指令後面再帶資料庫連線字串。

## 區域（Zone）管理

```bash
# 建立 Zone（會自動產生 SOA/NS）
pdnsutil create-zone example.com ns1.example.com

# 列出所有 Zone
pdnsutil list-all-zones

# 查看某個 Zone 內容
pdnsutil list-zone example.com

# 刪除 Zone（不可逆）
pdnsutil delete-zone example.com

# 升版本（序號 +1；SOA serial）
pdnsutil increase-serial example.com

# 變更 Zone 類型（Native/Master/Slave）
pdnsutil set-kind example.com Native
pdnsutil set-kind example.com Master
pdnsutil set-kind example.com Slave

```

## 紀錄（RRsets）管理

```bash
# 新增一筆紀錄
pdnsutil add-record example.com www A 300 203.0.113.10

# 取代整個 RRset（同名同型別的多筆紀錄一次重設）
pdnsutil replace-rrset example.com www A "300" "203.0.113.10" "203.0.113.11"

# 刪除整個 RRset
pdnsutil delete-rrset example.com www A

# 設定單筆紀錄 TTL（或用 replace-rrset 一次調整）
pdnsutil set-ttl example.com www A 600

```

## 主從複寫（AXFR）與通知

```bash
# 設定 Master 的來源 IP（Slave/Native 也可設）
pdnsutil set-masters example.com 10.0.0.10 10.0.0.11

# 設定通知對象（NOTIFY 將發送給這些 IP）
pdnsutil set-notify example.com 10.0.0.20 10.0.0.21

# 手動觸發 NOTIFY
pdnsutil notify example.com

# 加入 supermaster（供 Slave 自動加入）
pdnsutil add-supermaster 10.0.0.10 ns1.example.com accountA
pdnsutil list-supermasters
pdnsutil delete-supermaster 10.0.0.10

```

## 檢查、調整與匯入/匯出

```bash
# 檢查 Zone 合法性（語法/權威性/必要紀錄）
pdnsutil check-zone example.com

# 調整/整齊化紀錄（例如 DNSSEC/NSEC3 相關或大小寫等）
pdnsutil rectify-zone example.com

# 從 zonefile 匯入
pdnsutil load-zone example.com /path/to/zonefile

# 匯出為 zonefile（BIND 格式）
pdnsutil export-zone example.com /tmp/example.com.zone

```

## Meta 與額外屬性

```bash
# 設/查/清除 Zone meta（AXFR 策略、SOA 編輯模式等）
pdnsutil set-meta example.com SOA-EDIT-API INCEPTION-INCREMENT
pdnsutil get-meta example.com
pdnsutil clear-meta example.com SOA-EDIT-API

```

## DNSSEC（若需要）

```bash
# 以「API 模式」啟用 DNSSEC（變更由 API/工具觸發會自動調整 serial）
pdnsutil secure-zone --api example.com

# 建/列/刪 key
pdnsutil add-zone-key example.com ksk active rsasha256 2048
pdnsutil list-keys example.com
pdnsutil remove-zone-key example.com <keyid>

# NSEC3 啟用/調整
pdnsutil set-nsec3 example.com "1 0 10 abcd1234"   # 參數: flags optout iterations salt
pdnsutil unset-nsec3 example.com

# 關閉 DNSSEC（謹慎！）
pdnsutil disable-dnssec example.com

```

---

# PowerDNS Authoritative REST API 速查

## 啟用 API（pdns.conf）

你已經啟用，重點如下：

```ini
api=yes
api-key=你的長隨機字串
webserver=yes
webserver-address=0.0.0.0
webserver-port=8081
webserver-allow-from=0.0.0.0/0   # 建議上線時限縮來源段

```

> 變更後 `systemctl restart pdns`。呼叫 API 時請加 Header：`X-API-Key: <你的key>`

基底 URL 一般是：

```
http://<host>:8081/api/v1

```

伺服器 ID 預設是 `localhost`：

```
/api/v1/servers/localhost/...

```

## 範例：列出 Zone

```bash
curl -s -H 'X-API-Key: <APIKEY>' \
  http://127.0.0.1:8081/api/v1/servers/localhost/zones

```

## 建立 Zone（POST /zones）

```bash
curl -s -X POST -H 'X-API-Key: <APIKEY>' -H 'Content-Type: application/json' \
  -d '{
    "name": "example.com.",
    "kind": "Native",           // 也可 "Master" 或 "Slave"
    "masters": [],              // Slave 時填 Master IP 列表
    "nameservers": ["ns1.example.com.", "ns2.example.com."],
    "soa_edit_api": "INCEPTION-INCREMENT",
    "rrsets": [                 // 可選：同時帶入初始 RRsets
      {
        "name": "www.example.com.",
        "type": "A",
        "ttl": 300,
        "changetype": "REPLACE",
        "records": [
          {"content": "203.0.113.10", "disabled": false}
        ]
      }
    ]
  }' \
  http://127.0.0.1:8081/api/v1/servers/localhost/zones

```

## 修改/新增/刪除 RRsets（PATCH /zones/{zone}）

> 用 `changetype: REPLACE` 或 `DELETE`。`name` 要以「**尾巴有點**」的 FQDN。

```bash
curl -s -X PATCH -H 'X-API-Key: <APIKEY>' -H 'Content-Type: application/json' \
  -d '{
    "rrsets": [
      {
        "name": "www.example.com.",
        "type": "A",
        "ttl": 300,
        "changetype": "REPLACE",
        "records": [
          {"content": "203.0.113.10", "disabled": false},
          {"content": "203.0.113.11", "disabled": false}
        ]
      },
      {
        "name": "old.example.com.",
        "type": "A",
        "changetype": "DELETE",
        "records": []
      }
    ]
  }' \
  http://127.0.0.1:8081/api/v1/servers/localhost/zones/example.com.

```

## 讀取 Zone（GET /zones/{zone}）

```bash
curl -s -H 'X-API-Key: <APIKEY>' \
  http://127.0.0.1:8081/api/v1/servers/localhost/zones/example.com.

```

## 重新整理/通知

- 重新檢查/AXFR（Slave 取主檔）：`/zones/{zone}/axfr-retrieve`（POST）
- 發送 NOTIFY：`/zones/{zone}/notify`（POST）

```bash
# 例：觸發 NOTIFY
curl -s -X POST -H 'X-API-Key: <APIKEY>' \
  http://127.0.0.1:8081/api/v1/servers/localhost/zones/example.com./notify

```

## 統計 &amp; 健康

```bash
# 伺服器資訊
curl -s -H 'X-API-Key: <APIKEY>' \
  http://127.0.0.1:8081/api/v1/servers

# 統計值（QPS、響應碼等）
curl -s -H 'X-API-Key: <APIKEY>' \
  http://127.0.0.1:8081/api/v1/servers/localhost/statistics

```

## 清快取（Authoritative 有限；常見在 Recursor）

Authoritative 的 cache 能力有限；若啟用 packet/negcache，可用：

```bash
curl -s -X PUT -H 'X-API-Key: <APIKEY>' \
  http://127.0.0.1:8081/api/v1/servers/localhost/cache/flush

```

---

# 常見實務小提醒

- 你把服務綁在 `local-port=54`，本機測試 `dig` 要記得 `-p 54`。
- API 的 `name`、`nameservers`、`zone` **FQDN 都要以 `.` 結尾**（PDNS API 慣例）。
- `webserver-allow-from=0.0.0.0/0` 方便測試，但上線環境務必限縮來源段或套防火牆。
- 改 RRsets 後若 `SOA-EDIT-API` 有設會自動處理 serial；否則記得用 `increase-serial`。
- 若搭配 Recursor/外層 dnsdist，有時 Authoritative 的變更需要幾秒才被上層 cache 反映。

---

# RFC

# 前端相關

# 【Vue】【Rect】生態圈相關名詞

以下是 **Vue 與 React 生態圈的比較表**，幫助你快速了解兩者的主要工具和技術差異：

<table id="bkmrk-%E9%A1%9E%E5%88%A5-vue-%E7%94%9F%E6%85%8B%E5%9C%88-react-%E7%94%9F%E6%85%8B%E5%9C%88"><thead><tr><th>類別</th><th>Vue 生態圈</th><th>React 生態圈</th></tr></thead><tbody><tr><td>**核心框架**</td><td>Vue.js（漸進式前端框架）</td><td>React（Meta 開發的 UI 庫）</td></tr><tr><td>**進階框架（SSR / SSG）**</td><td>Nuxt.js（支援 SSR、SSG、SPA）</td><td>Next.js（支援 SSR、SSG、API Routes）</td></tr><tr><td> </td><td> </td><td>Remix（更強調伺服器端渲染）</td></tr><tr><td>**狀態管理**</td><td>Vuex（官方舊方案，已被取代）</td><td>Redux / Redux Toolkit（最流行，但繁瑣）</td></tr><tr><td> </td><td>Pinia（官方推薦，比 Vuex 簡單）</td><td>Recoil（Facebook 開發，簡單高效）</td></tr><tr><td> </td><td>Vue Composition API（內建狀態管理）</td><td>Zustand（輕量簡單，替代 Redux）</td></tr><tr><td> </td><td>Vue Query（類似 React Query）</td><td>React Query（最佳 API 資料管理）</td></tr><tr><td>**路由管理**</td><td>Vue Router（官方路由）</td><td>React Router（官方路由，適合 SPA）</td></tr><tr><td>**UI 框架**</td><td>Vuetify（Material Design）</td><td>Material UI（MUI，Google Material Design）</td></tr><tr><td> </td><td>Element Plus（適合後台）</td><td>Ant Design（企業級 UI 框架）</td></tr><tr><td> </td><td>Quasar（跨平台支援）</td><td>Chakra UI（彈性設計，易用）</td></tr><tr><td> </td><td>Tailwind CSS（支援 Vue）</td><td>Tailwind CSS（最流行的 CSS 框架）</td></tr><tr><td>**HTTP 請求 &amp; API 管理**</td><td>Axios（最常用的 HTTP 客戶端）</td><td>Axios（最常用）</td></tr><tr><td> </td><td>Vue Query（類似 React Query）</td><td>SWR（Next.js 推薦，輕量 API 快取）</td></tr><tr><td>**動畫 &amp; 特效**</td><td>Vue Transition（內建動畫）</td><td>Framer Motion（最流行的動畫庫）</td></tr><tr><td> </td><td>GSAP（強大的動畫庫）</td><td>GSAP（適合複雜動畫）</td></tr><tr><td>**開發工具**</td><td>Vite（Vue 3 預設建構工具）</td><td>Vite（React 也推薦使用）</td></tr><tr><td> </td><td>Vue CLI（Vue 2 舊版建構工具）</td><td>Create React App（CRA，舊版 React 專案用）</td></tr><tr><td> </td><td>Vue DevTools（瀏覽器開發工具）</td><td>React DevTools（官方瀏覽器工具）</td></tr><tr><td>**測試工具**</td><td>Vitest（Vue 3 單元測試）</td><td>Jest（最常用的 React 測試框架）</td></tr><tr><td> </td><td>Vue Testing Library（測試 Vue 組件）</td><td>React Testing Library（測試 React 組件）</td></tr><tr><td> </td><td>Cypress（E2E 測試）</td><td>Cypress（E2E 測試）</td></tr><tr><td>**部署 &amp; 靜態網站**</td><td>VuePress（靜態網站生成）</td><td>Gatsby（React 靜態網站框架）</td></tr><tr><td> </td><td>Nuxt Content（Markdown 內容管理）</td><td>Docusaurus（React 文件網站）</td></tr><tr><td> </td><td>Vercel / Netlify（雲端部署）</td><td>Vercel / Netlify（雲端部署）</td></tr><tr><td>**新技術 &amp; 特性**</td><td>Composition API（Vue 3 核心變革）</td><td>React Concurrent Mode（提升渲染效能）</td></tr><tr><td> </td><td>Teleport（將組件渲染到指定 DOM）</td><td>React Server Components（適用於 Next.js）</td></tr><tr><td> </td><td>Suspense（處理異步請求）</td><td>Suspense（類似概念，處理異步）</td></tr><tr><td>**架構模式**</td><td>CSR（傳統 SPA，SEO 較差）</td><td>CSR（傳統 React SPA）</td></tr><tr><td> </td><td>SSR（Nuxt.js 提供）</td><td>SSR（Next.js / Remix 提供）</td></tr><tr><td> </td><td>SSG（Nuxt 也支援）</td><td>SSG（Next.js / Gatsby 支援）</td></tr></tbody></table>

**🔹 總結：**

- Vue 適合 **簡單上手、開發體驗佳** 的專案，**Nuxt.js 是 SSR / SSG 的最佳選擇**。
- React 擁有 **更大的生態圈**，Next.js 和 Remix 提供 **更強的伺服器端能力**，適合**大型應用**。
- **Vue 適合小型 / 中型專案，React 更適合大規模應用**。

如果你對某個領域（如 **SSR、狀態管理、測試**）想進一步了解，歡迎問我！🔥

# 【名詞解釋】【JS】Tree-Shaking (搖樹優化)

<div id="bkmrk-"><div>---

</div></div>Tree-Shaking (搖樹優化) 是一種在前端開發中非常重要的**程式碼優化技術**，它主要用於 **JavaScript 模組**。它的核心思想是**消除那些最終應用程式中沒有使用的程式碼**，從而減少打包後的檔案大小，提升網頁加載速度和執行效率。

### 什麼是 Tree-Shaking？

你可以把 Tree-Shaking 想像成一棵程式碼樹。你的專案代碼就是這棵樹的主幹和分枝，而你從各個模組中引入的函式、變數等，就是這棵樹上的葉子。

**Tree-Shaking 的目的就是「搖掉」那些從未被使用的「葉子」**。

在傳統的 JavaScript 模組中，即使你只引入了一個庫中的一個函式，整個庫的程式碼也可能會被打包進來。但有了 Tree-Shaking，打包工具能夠分析你的程式碼，識別出哪些部分實際被使用了（被「引用」或「匯入」），哪些部分從未被使用。最終，那些未被使用的程式碼就會在打包時被移除，就像秋天樹葉被搖落一樣。

### Tree-Shaking 的運作原理

Tree-Shaking 的實現依賴於 **ES Modules (ESM)** 的靜態分析特性。

<div id="bkmrk-%E9%9D%9C%E6%85%8B%E5%88%86%E6%9E%90-%28static-analysi"><div>1. 靜態分析 (Static Analysis)：
    
    ES Modules (使用 import 和 export 語法) 允許打包工具在編譯時期（而不是執行時期）分析模組之間的依賴關係。這意味著打包工具能夠清楚地知道每個模組輸出了什麼，以及你的程式碼從其他模組導入了什麼。
2. 標記 (Marking)：
    
    打包工具（例如 Webpack、Rollup）會從你的應用程式的入口點開始，追蹤所有 import 和 export 語句。它會遍歷所有的程式碼，將實際被使用到的程式碼片段標記為「已使用」(used)。
3. 清除 (Sweeping)：
    
    在標記完成後，打包工具會將所有未被標記為「已使用」的程式碼片段從最終的打包檔案中移除。這些未被使用的程式碼被視為「死程式碼」(dead code)。

</div></div>**重點：** Tree-Shaking 只有在你的程式碼是 **ES Modules 語法**時才能有效運作。這是因為 CommonJS (使用 `require` 和 `module.exports`) 等其他模組系統是動態載入的，打包工具難以在編譯時確定哪些程式碼會被使用。

### 誰在做 Tree-Shaking？

Tree-Shaking 主要由**現代化的模組打包工具 (Module Bundlers)** 來實現，其中最著名的就是：

<div id="bkmrk-webpack-%28%E7%89%88%E6%9C%AC-2-%E5%8F%8A%E4%BB%A5%E4%B8%8A%29%EF%BC%9Aw"><div>- **Webpack (版本 2 及以上)**：Webpack 預設支援 Tree-Shaking，但在實際使用中，為了達到最佳效果，你可能還需要做一些配置，例如啟用 `optimization.usedExports` 和確保你的 Babel 配置不會將 ES Modules 轉換為 CommonJS。
- **Rollup**：Rollup 在設計之初就強調了 Tree-Shaking 的能力，它通常被認為在庫的打包方面效果更好，因為它能生成更小、更扁平的程式碼。
- **Vite**：Vite 在開發模式下利用瀏覽器的 ESM 原生支援，在生產環境則使用 Rollup 進行打包，因此也具備出色的 Tree-Shaking 能力。

</div></div>### Tree-Shaking 的好處

<div id="bkmrk-%E6%9B%B4%E5%B0%8F%E7%9A%84%E6%AA%94%E6%A1%88%E5%A4%A7%E5%B0%8F%EF%BC%9A-%E9%80%99%E6%98%AF%E6%9C%80%E7%9B%B4%E6%8E%A5%E7%9A%84%E5%A5%BD%E8%99%95%EF%BC%8C%E7%A8%8B%E5%BC%8F"><div>- **更小的檔案大小：** 這是最直接的好處，程式碼量減少，下載所需時間也減少。
- **更快的加載速度：** 瀏覽器需要下載和解析的 JavaScript 檔案更小，網頁加載速度自然更快。
- **更低的執行成本：** 瀏覽器需要執行的程式碼更少，降低了 CPU 和記憶體的使用。
- **更好的使用者體驗：** 網站響應更快，用戶體驗更流暢。

</div></div>### 實際應用中的注意事項

為了確保 Tree-Shaking 能夠發揮最大效果，你需要注意以下幾點：

<div id="bkmrk-%E4%BD%BF%E7%94%A8-es-modules-%E8%AA%9E%E6%B3%95%EF%BC%9A-%E7%A2%BA%E4%BF%9D"><div>1. **使用 ES Modules 語法：** 確保你的專案程式碼和引用的第三方庫都使用 `import` 和 `export` 語法。
2. **配置 Babel (或 TypeScript)：** 如果你使用 Babel 或 TypeScript 轉換程式碼，請確保它們**不會將 ES Modules 轉換為 CommonJS 模組**，否則打包工具將無法進行靜態分析。在 Babel 中，這通常意味著將 `@babel/preset-env` 中的 `modules` 選項設定為 `false`。
3. **確認第三方庫是否支援 Tree-Shaking：** 有些第三方庫可能沒有設計成 Tree-Shaking 友好的形式。理想情況下，庫會在其 `package.json` 中設定 `sideEffects: false`，表明其程式碼沒有副作用，可以安全地進行 Tree-Shaking。
4. **避免副作用 (Side Effects)：** 包含副作用的程式碼可能會阻止 Tree-Shaking。例如，直接在模組的頂層執行一個函式可能會被打包工具視為副作用，即使這個函式本身沒有被顯式引用，也可能不會被移除。

---

</div></div>總之，Tree-Shaking 是一種利用 ES Modules 靜態分析特性，移除未使用的程式碼，從而優化前端應用程式效能的關鍵技術。理解並正確配置它，對於構建高效能的網頁應用程式至關重要。

# 【轉載】什麼是 Virtual DOM？從底層原理到 React 的渲染策略

#### <span data-position="596" data-size="21">出處：[https://hackmd.io/xejlCpUsQaibO90CYB5CJg](https://hackmd.io/xejlCpUsQaibO90CYB5CJg)</span>

## <span data-position="43" data-size="18">為什麼要談 Virtual DOM？</span>

<span data-position="63" data-size="141">每當我們在網頁上看到動態更新的內容，背後都可能涉及了對 DOM (文件物件模型) 的操作。然而，DOM 與瀏覽器的渲染引擎是緊密耦合的，任何一次看似微小的改動，都可能觸發瀏覽器進行成本高昂的 重排 (Reflow) 與 重繪 (Repaint)，頻繁的操作最終會成為應用的效能瓶頸。</span>

<span data-position="206" data-size="81">為了在提供豐富互動的同時，又能維持流暢的體驗，前端框架必須找到一個方法來「最小化對真實 DOM 的操作」。而 Virtual DOM，正是為此而生的一種優雅策略。</span>

## <span data-position="292" data-size="13">DOM 是什麼？為什麼慢？</span>

<span data-position="307" data-size="48">很間單的先用一句話說明是：瀏覽器呈現 UI 的載體。但在我整理資料之後我覺得可以用更精細的說明：</span>

1. <span data-position="359" data-size="91">網頁瀏覽器內部的 HTML 解析器(parsing)會將原始的 HTML/XML 文本解析並轉換為一個樹狀的資料結構，這就是 Document Object Model (DOM)。</span>
2. <span data-position="454" data-size="64">DOM 的每個節點都代表文件中的一部分，並以 JavaScript 物件的形式存在，提供了一系列屬性與方法 (DOM API)。</span>
3. <span data-position="522" data-size="67">JavaScript 正是透過這些 DOM API 來存取、查詢、修改這些節點，從而動態地控制網頁的內容、樣式和行為，實現使用者互動。</span>

### <span data-position="596" data-size="21">麼是 Reflow / Repaint？</span>

- **<span data-position="622" data-size="11">回流 (Reflow)</span>**<span data-position="635" data-size="59">：當 DOM 元素的幾何屬性（如寬度、高度、位置）發生改變，導致瀏覽器需要重新計算元素在頁面上的佈局時，就會觸發回流。</span>
- **<span data-position="699" data-size="12">重繪 (Repaint)</span>**<span data-position="713" data-size="56">：當元素的外觀屬性（如顏色、背景）發生改變，但不影響其佈局時，瀏覽器只需重新繪製該部分的外觀，這個過程稱為重繪。</span>
- <span data-position="772" data-size="39">回流必定會觸發重繪，但重繪不一定會觸發回流。因此，回流對效能的影響遠大於重繪。</span>

### <span data-position="817" data-size="18">每次 DOM 變化都可能觸發效能瓶頸</span>

<span data-position="837" data-size="78">瀏覽器在操作 DOM 的過程中都有可能引發 Reflow 或 Repaint，過程中是仰賴 瀏覽器CPU 需要重新計算，在反覆的過程中是可能引起效能瓶頸的。</span>

[![image-1752027656034.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/scaled-1680-/FYgkAI1tEn0mHiM9-image-1752027656034.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/FYgkAI1tEn0mHiM9-image-1752027656034.png)

其餘內容請看原站～～

# IM 相關

# 【slack】上傳檔案

#### **找到 CHANNEL\_ID**

[![image-1741855326388.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-03/scaled-1680-/qAkFxsyhs02Aa1wG-image-1741855326388.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-03/qAkFxsyhs02Aa1wG-image-1741855326388.png)

#### **找到 SLACK\_BOT\_TOKEN**

[![image-1741855202204.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-03/scaled-1680-/5y8eAFk89XYUS2QO-image-1741855202204.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-03/5y8eAFk89XYUS2QO-image-1741855202204.png)

如果tocken權限不足，要往下拉確認一下 bot scopes 權限

[![image-1741855214090.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-03/scaled-1680-/TTtpV3geH2OsjcEW-image-1741855214090.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-03/TTtpV3geH2OsjcEW-image-1741855214090.png)

#### 準備 python 環境

```shell
echo 'slack-sdk>=3.12,<4' > requirements.txt
python3 -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install -r requirements.txt
```

#### upload.py (注意參數是<span style="color: #e03e2d;">channel</span> 不是 <span style="text-decoration: line-through;">channel\_id</span> <span style="color: #e03e2d;">官方文件錯誤</span>)

```Python
import logging, os

logging.basicConfig(level=logging.DEBUG)

from slack_sdk import WebClient
SLACK_BOT_TOKEN = "xoxb-*******************"
CHANNEL_ID = "C01*******"

client = WebClient(SLACK_BOT_TOKEN)

upload_text_file = client.files_upload_v2(
    channel=CHANNEL_ID,
    # title="Test text data",
    file="./response.png",
    initial_comment="告警結果",
)
```

[![image-1741855246370.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-03/scaled-1680-/t9ta0IcLulSePPLX-image-1741855246370.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-03/t9ta0IcLulSePPLX-image-1741855246370.png)

[![image-1741855268581.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-03/scaled-1680-/El8BrpybDNmLAbBO-image-1741855268581.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-03/El8BrpybDNmLAbBO-image-1741855268581.png)

參考 ：  
官方網站  
[https://tools.slack.dev/python-slack-sdk/tutorial/uploading-files](https://tools.slack.dev/python-slack-sdk/tutorial/uploading-files)

官方網站-文件錯誤  
[https://github.com/slackapi/python-slack-sdk/issues/1608](https://github.com/slackapi/python-slack-sdk/issues/1608)

# 【slack】相關連結

https://app.slack.com/block-kit-builder

# Sentry

# 【Sentry】安裝 Sentry (docker)

Sentry 是一個開源的錯誤追蹤系統，可用於收集應用程式中的例外與錯誤資訊。你可以透過 Docker 或直接在 Ubuntu 上安裝。以下是兩種方式的詳細步驟。

---

## ✅ 使用 Docker 安裝 Sentry

Sentry 官方推薦使用 Docker 來部署，方便快速、維護簡單。

### 1. 安裝 Docker 與 Docker Compose（若尚未安裝）

```bash
sudo apt update
sudo apt install docker.io docker-compose -y
sudo systemctl enable docker
sudo systemctl start docker

```

### 2. 下載 Sentry 安裝腳本

```bash
git clone https://github.com/getsentry/self-hosted.git sentry-self-hosted
cd sentry-self-hosted

```

### 3. 設定環境變數

你可以修改 `.env` 或使用預設值即可。

### 4. 啟動安裝腳本

```bash
./install.sh

```

這個腳本會下載相關 images，初始化資料庫與設定等，需要一點時間（約 10–30 分鐘）。

### 5. 啟動服務

```bash
docker-compose up -d

```

### 6. 瀏覽器開啟 Sentry UI

打開 `http://<your-server-ip>:9000`（預設埠號是 `9000`），開始進行帳號註冊與專案設定。

---

## ✅ 在 Ubuntu 上原生安裝 Sentry（不建議，較複雜）

Sentry 的原生安裝比較繁瑣，不建議除非你有特別需求（如不使用 Docker 的環境）。以下是基本流程：

### 1. 安裝必要套件

```bash
sudo apt update
sudo apt install python3 python3-pip python3-venv redis postgresql libpq-dev build-essential -y

```

### 2. 建立 PostgreSQL 資料庫

```bash
sudo -u postgres psql
CREATE DATABASE sentry;
CREATE USER sentry WITH PASSWORD 'sentry';
GRANT ALL PRIVILEGES ON DATABASE sentry TO sentry;
\q

```

### 3. 安裝並設定虛擬環境

```bash
python3 -m venv sentry-venv
source sentry-venv/bin/activate
pip install --upgrade pip setuptools wheel

```

### 4. 安裝 Sentry

```bash
pip install sentry

```

### 5. 初始化 Sentry

```bash
sentry upgrade

```

這一步會要求你建立一個帳號。

### 6. 啟動服務

```bash
sentry run web

```

你可以另開終端機，啟動 worker 與 cron：

```bash
sentry run worker
sentry run cron

```

你可能還需要用 `nginx` 或 `gunicorn` 建立完整服務。

---

## ✅ 總結比較

<table id="bkmrk-%E6%96%B9%E5%BC%8F-%E5%84%AA%E9%BB%9E-%E7%BC%BA%E9%BB%9E-docker-%E5%BF%AB%E9%80%9F%E3%80%81%E5%AE%98"><thead><tr><th>方式</th><th>優點</th><th>缺點</th></tr></thead><tbody><tr><td>Docker</td><td>快速、官方支援、易維護</td><td>需安裝 Docker</td></tr><tr><td>原生 Ubuntu</td><td>更靈活、自行控管細節</td><td>安裝繁瑣、難維護、容易踩雷</td></tr></tbody></table>

---

# Sentry CSRF Verification Failed

  
https://github.com/getsentry/self-hosted/issues/2865

[![image-1744006079789.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-04/scaled-1680-/DFmLd089WxhRB3AO-image-1744006079789.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-04/DFmLd089WxhRB3AO-image-1744006079789.png)

```shell
# 出現錯誤，請在以下檔案
sentry/sentry.conf.py
# 填入你機器真實ip:port
CSRF_TRUSTED_ORIGINS = ["sentry.mydomain.com", "127.0.0.1:9000"]
ex.
CSRF_TRUSTED_ORIGINS = ["10.10.0.1:9000"]
```

# 【Sentry】 自架主機分析

當你要自建 Sentry 並希望個別安裝各個服務（非 all-in-one Docker 方式），需要了解 Sentry 背後實際上是一個 **分散式系統**，包含多個子服務。以下是簡要說明每個服務的用途，並提供 **最小部署需求建議（幾台機器）**。

---

## 🧩 Sentry 核心元件說明

<table id="bkmrk-%E6%9C%8D%E5%8B%99-%E7%94%A8%E9%80%94-%E6%98%AF%E5%90%A6%E5%BF%85%E8%A6%81-%E8%AA%AA%E6%98%8E-sentry"><thead><tr><th>服務</th><th>用途</th><th>是否必要</th><th>說明</th></tr></thead><tbody><tr><td>**Sentry Web**</td><td>Web UI + API</td><td>✅ 必要</td><td>使用者操作介面與事件接收入口。</td></tr><tr><td>**Worker**</td><td>背景任務處理</td><td>✅ 必要</td><td>發送通知、儲存事件、標記等。Celery 背後用 Redis 當 broker。</td></tr><tr><td>**Cron**</td><td>定期任務排程</td><td>✅ 必要</td><td>例如清除舊資料、定期檢查等。</td></tr><tr><td>**Relay**</td><td>邊界接收器</td><td>✅ 建議</td><td>前端 SDK 最先接觸的入口，可作為 proxy、限流、預處理。</td></tr><tr><td>**PostgreSQL**</td><td>資料庫</td><td>✅ 必要</td><td>儲存大部分結構化資料（專案、使用者、事件等）。</td></tr><tr><td>**Redis**</td><td>訊息佇列、快取</td><td>✅ 必要</td><td>Celery 任務排程、快取使用。</td></tr><tr><td>**Kafka**</td><td>事件串流</td><td>✅ 必要（新版）</td><td>訊息串流 backbone，傳輸異常事件。</td></tr><tr><td>**Zookeeper**</td><td>Kafka 依賴</td><td>✅ Kafka 必要</td><td>Kafka metadata 管理工具。</td></tr><tr><td>**ClickHouse**</td><td>高速查詢</td><td>✅ 建議</td><td>儲存事件 metadata，提供 Discover/Performance UI。</td></tr><tr><td>**Symbolicator**</td><td>原始碼還原符號</td><td>🟡 可選</td><td>用於原生 crash report（iOS/Android 原始符號對應）。</td></tr><tr><td>**Snuba**</td><td>查詢引擎</td><td>✅ 必要</td><td>Sentry 的查詢服務，轉發查詢給 ClickHouse。</td></tr><tr><td>**nginx**</td><td>Proxy</td><td>✅ 建議</td><td>當作入口，轉發請求給 Sentry、Relay 等。</td></tr></tbody></table>

---

## 📦 最小分布式部署建議（分機版本）

<table id="bkmrk-%E7%AF%80%E9%BB%9E-%E5%AE%89%E8%A3%9D%E9%A0%85%E7%9B%AE-%E8%AA%AA%E6%98%8E-node-1%EF%BC%9Aap"><thead><tr><th>節點</th><th>安裝項目</th><th>說明</th></tr></thead><tbody><tr><td>**Node 1：App Server**</td><td>`sentry-web` + `cron` + `worker`</td><td>最好有 2 核以上，主要承擔使用者 UI 與邏輯</td></tr><tr><td>**Node 2：資料庫**</td><td>PostgreSQL + Redis</td><td>可整合在同台，需穩定，重要資料所在</td></tr><tr><td>**Node 3：訊息處理**</td><td>Kafka + Zookeeper</td><td>Kafka 可與其他服務共用機器（但建議獨立）</td></tr><tr><td>**Node 4：查詢引擎**</td><td>Snuba + ClickHouse</td><td>查詢速度核心，可以跟 Kafka 共用</td></tr><tr><td>**Node 5：Symbolicator + Relay**</td><td>可再分出獨立機器</td><td>可選，針對原生應用及 SDK proxy</td></tr></tbody></table>

🧠 **最小建議：**  
最少可以從 **3 台機器** 起步：

- **機器 A（App）**：sentry-web、cron、worker、relay
- **機器 B（Storage）**：PostgreSQL、Redis、Symbolicator
- **機器 C（Streaming）**：Kafka、Zookeeper、ClickHouse、Snuba

這樣就可以支撐基本功能。當負載上來之後，再做橫向擴展。

---

## ✅ 補充：資源建議（單位：最小）

<table id="bkmrk-%E6%9C%8D%E5%8B%99-cpu-memory-%E7%A3%81%E7%A2%9F-pos"><thead><tr><th>服務</th><th>CPU</th><th>Memory</th><th>磁碟</th></tr></thead><tbody><tr><td>PostgreSQL</td><td>2C</td><td>4GB+</td><td>SSD 儲存，100GB 起跳</td></tr><tr><td>ClickHouse</td><td>2C</td><td>4GB+</td><td>SSD 快速查詢，空間依事件量調整</td></tr><tr><td>Kafka</td><td>2C</td><td>4GB</td><td>10GB 起</td></tr><tr><td>Snuba</td><td>1C</td><td>2GB</td><td>低</td></tr><tr><td>Redis</td><td>1C</td><td>1GB</td><td>幾乎沒磁碟需求</td></tr><tr><td>Web / Worker</td><td>2C</td><td>2~4GB</td><td>看使用者數</td></tr></tbody></table>

---

## 🧭 小結

<table id="bkmrk-%E6%83%B3%E6%B3%95-%E5%BB%BA%E8%AD%B0-%E5%8F%AA%E6%98%AF%E8%A6%81%E3%80%8C%E5%BF%AB%E9%80%9F%E7%8E%A9%E7%8E%A9%E7%9C%8B%E3%80%8D-%E2%9C%85-d"><thead><tr><th>想法</th><th>建議</th></tr></thead><tbody><tr><td>只是要「快速玩玩看」</td><td>✅ Docker self-hosted（1 台）</td></tr><tr><td>內網 demo、準生產環境</td><td>✅ 拆 3 台機器，參考上表</td></tr><tr><td>正式環境、日誌量大</td><td>✅ 至少 5 台，分出 ClickHouse 與 Kafka</td></tr><tr><td>已有 K8s 架構</td><td>✅ 用 Helm Chart 部署</td></tr></tbody></table>

---

# AWS 相關

# 【AWS】Day 2：AWS 的基本知識

# Day 2：AWS 的基本知識

上一篇文章我們大致介紹了什麼是雲端服務，並且認識了幾個主流的雲端服務商，像是 AWS、GCP、Azure……等等。

這篇文章則會來介紹 AWS（Amazon Web Services）裡面的一些基本概念，包括：

- AWS 的 Region 和 Availability Zone（簡稱 AZ）
- AWS 上的資源範圍（Region、AZ、Global）
- 管理 AWS 資源的方法有哪些

---

## Region 和 Availability Zone 是什麼？

AWS 和 GCP 一樣，也使用了類似的概念來區分地理區域，他們稱作：

- **Region（區域）**
- **Availability Zone（可用區）**

### Region（區域）

Region 就是地理位置的概念，例如：

- 台灣最近的是 **Asia Pacific (Tokyo)**，代碼是 `ap-northeast-1`
- 新加坡是 `ap-southeast-1`
- 美國東岸的維吉尼亞北部是 `us-east-1`（也是最多 AWS 服務最早上線的 Region）

截至 2023 年，AWS 在全球已經有超過 **30 個以上的 Region**，每個 Region 都是 AWS 自建的實體資料中心群。

### Availability Zone（AZ，可用區）

每一個 Region 底下會有多個 Availability Zones（通常 2~6 個），可以想像成是一個個獨立的資料中心（不同大樓、不同電力、不同網路線路），目的是增加可靠性。

舉例來說：

- `us-east-1` 底下就有 `us-east-1a`、`us-east-1b`、`us-east-1c`……等 AZ
- `ap-southeast-1`（新加坡）底下有 `ap-southeast-1a`、`ap-southeast-1b`、`ap-southeast-1c`

AZ 是 AWS 用來實現高可用（High Availability）的基本單位。服務部署在多個 AZ，可以避免單一機房故障造成整個應用中斷。

---

## 資源範圍：Region / AZ / Global 的區分

在 AWS 上，每一種服務在建立時都有自己的作用範圍，分為三種：

### 🔹 以 AZ 為單位的服務

像是 EC2（虛擬機）、EBS（區塊儲存）就是屬於這一類。

- 在建立 EC2 時，你需要選擇一個 AZ，像是 `ap-northeast-1a`
- EBS 只能掛載在同一個 AZ 裡的 EC2
- 如果你要做高可用，就要部署在多個 AZ，並自己設計 failover 機制

### 🔸 以 Region 為單位的服務

像是 S3（物件儲存）、RDS（資料庫）、Elastic Load Balancer、VPC 網路等等，都是 Region 級別的服務。

- 在一個 Region 中建立的 S3 bucket，可以讓這個 region 內的資源共同使用
- RDS 則可以設定 Multi-AZ（跨 AZ 高可用）

### 🌐 以 Global 為單位的服務

有些服務是全域性的，例如：

- IAM（身份與權限管理）
- Route 53（DNS 服務）
- CloudFront（CDN 服務）

這些服務在建立時不會要求你選擇 Region，它們的作用範圍就是「整個 AWS 帳號或全球網路」。

---

## 管理 AWS 的四種方式

![](https://docs.aws.amazon.com/assets/images/aws-management-tools.png)

在 AWS 上，我們有四種主要方式可以操作與管理服務：

### 1. AWS Management Console（管理主控台）

透過瀏覽器進入 [https://console.aws.amazon.com](https://console.aws.amazon.com)，就可以用圖形化介面來操作各種資源。

是最簡單、最直覺的操作方式。

### 2. AWS CLI（Command Line Interface）

透過終端機安裝 CLI 工具（`aws` 指令），可以用命令列操作 AWS 的服務。

適合自動化部署、寫腳本、CI/CD 等情境。

### 3. AWS SDKs

AWS 提供多種語言的開發套件（Java, Python, Node.js, Go…等），可以直接透過程式碼呼叫 AWS API。

適合建構自動化應用或將 AWS 整合進你自己的系統。

### 4. AWS Mobile App

AWS 官方也有提供手機 App，可以用來查看服務狀態、成本、帳單、甚至手動操作某些資源。

---

## 小結

這篇文章我們介紹了 AWS 中的基本概念，包括：

- Region 和 Availability Zone 的差別
- 服務根據作用範圍可以分為 AZ 級、Region 級與 Global 級
- 常見的四種管理 AWS 的方法

理解這些概念之後，會對後續的服務操作更容易上手，也比較知道為什麼某些設定不能跨 AZ 或跨 Region。

---

那麼下一篇文章，我們就會進一步來介紹，在 AWS 中的帳號與 Project 結構（包含 Account、Organization、OU、Tagging 等觀念），幫助你理解大型團隊怎麼管理 AWS 資源。

我們下篇見啦～👋

# 【AWS】Day 3：AWS 中的帳號與資源管理架構

上一篇我們介紹了 AWS 的基本概念，包括什麼是 Region、Availability Zone，以及怎麼操作 AWS 的幾種方式。

那這篇文章，我們要來聊聊一個很多人初學 AWS 時會感到困惑的問題：**AWS 裡面的資源，到底是怎麼被組織與管理的？**

---

## 為什麼要有架構？

當你只是個人開一個帳號、開幾台機器、弄幾個服務，帳號的資源大致上不會太複雜，console 點一點都還可以處理。

但當你是企業使用者、跨部門團隊、有多個環境（dev / uat / prod）、多個專案、甚至上百個服務時，就會遇到一堆問題：

- 權限怎麼分？
- 帳單怎麼分？
- 誰能看？誰能用？
- 怎麼防止誤刪 / 誤操作？

這時候就會用到 AWS 提供的資源管理架構囉～

---

## AWS 的三層資源管理架構

在 GCP 中，我們常會提到 **Project** 的概念。但在 AWS 裡面，這樣的概念其實是被拆成以下幾層來實現的：

### 🔑 1. Account（AWS 帳號）

AWS 帳號是最基本的管理單位。每個帳號就是一個獨立的資源空間，擁有自己的：

- IAM 設定（權限、角色）
- 資源與服務（EC2、S3、RDS 等等）
- 帳單與計費紀錄

可以想像成一個帳號就是一間房間，裡面什麼資源你都自己負責。

👉 很多企業會用多帳號來隔離環境，例如：

- 一個帳號放開發環境（dev）
- 一個帳號放測試環境（uat）
- 一個帳號放生產環境（prod）

這樣的好處是資源更隔離、權限更清楚、帳單也更容易拆分。

---

### 🏢 2. AWS Organizations（組織）

AWS Organizations 是一個多帳號的管理工具，讓你可以統一控管多個 AWS 帳號，並集中管理費用與權限。

使用 Organizations 後，可以做到：

- 建立帳號之間的層級架構（像是 OU = 組織單位）
- 統一帳單（Consolidated Billing）
- 實作 SCP（Service Control Policies）來限制某些帳號能用的服務

👉 這就像是你開了一間公司，然後底下開了一堆子公司，每個子公司有自己的帳號，但你這個總部可以看所有分公司的帳單，也能下政策規定大家不能亂搞。

---

### 🏷️ 3. Resource Tag（資源標籤）

這個就是 AWS 用來補足「Project」概念的做法。

因為 AWS 沒有像 GCP 的 Project 架構，所以會建議大家在建立資源時，**養成加上標籤（Tag）的習慣**！

常見的標籤包含：

- `Project`: live-api、momo-shop、data-pipeline
- `Env`: dev、uat、prod
- `Owner`: treeman、team-data、ops-team
- `CostCenter`: 90123、IT-Backend、Marketing

透過 Tag：

- 可以更清楚知道這台機器是做什麼的
- 可以用在 **成本分析**（Cost Explorer）
- 可以配合 IAM 權限做限制（例如只能操作特定 Project 的資源）
- 還能透過工具（如 Config、Budgets）追蹤管理

---

## 小結

這篇文章我們介紹了 AWS 的資源管理架構，總結如下：

<div id="bkmrk-%E6%A6%82%E5%BF%B5%E5%90%8D%E7%A8%B1-%E5%B0%8D%E6%87%89%E7%94%A8%E9%80%94-account-%E8%B3%87%E6%BA%90"><button class="hover:bg-token-main-surface-secondary text-token-text-secondary pointer-events-auto rounded-lg px-1 py-1 opacity-0 transition-opacity duration-200 group-focus-within:opacity-100 group-hover:opacity-100"><svg class="icon-md-heavy" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg></button><div><table class="min-w-full" data-end="1805" data-start="1576"><thead data-end="1620" data-start="1576"><tr data-end="1620" data-start="1576"><th data-end="1595" data-start="1576">概念名稱</th><th data-end="1620" data-start="1595">對應用途</th></tr></thead><tbody data-end="1805" data-start="1674"><tr data-end="1714" data-start="1674"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1697" data-start="1674">**Account**</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1714" data-start="1697">資源與權限的邊界、計費單位</td></tr><tr data-end="1759" data-start="1715"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1738" data-start="1715">**Organizations**</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1759" data-start="1738">多帳號統一管理架構</td></tr><tr data-end="1805" data-start="1760"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1783" data-start="1760">**Tag**</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1805" data-start="1783">資源標記，模擬 Project 分類</td></tr></tbody></table>

</div></div>其實 AWS 的架構雖然比 GCP 稍微複雜一點，但也因為彈性大，更能符合企業或大型專案的需求。

---

那麼下一篇文章，我們就正式進入 **AWS 的運算服務介紹** 啦！包括 EC2、Lambda、ECS、EKS、App Runner……每一種服務適合什麼情境、優缺點是什麼，我們下一篇文章再慢慢拆解！

我們 Day 4 再見啦 👋

# 【AWS】Day 4：AWS 運算服務簡介

# Day 4：AWS 運算服務簡介

在上一篇我們認識了 AWS 中的資源管理架構，包括 Account、Organizations、以及 Tag 的概念。

接下來我們要進入一個非常重要的主題：**AWS 的運算服務（Compute Services）**

所謂「運算服務」，指的就是你在雲端上 **執行應用程式、後端系統、作業流程** 的基礎設施，也就是「跑程式碼的地方」。

---

## AWS 運算服務的類型有哪些？

AWS 提供非常多樣化的運算服務，為了對應不同的使用情境與技術需求，主要可以分成以下幾大類：

<div id="bkmrk-%E5%88%86%E9%A1%9E-%E8%AA%AA%E6%98%8E-%E4%BB%A3%E8%A1%A8%E6%9C%8D%E5%8B%99-iaas-%E8%87%AA%E5%B7%B1%E7%AE%A1%E7%90%86"><button class="hover:bg-token-main-surface-secondary text-token-text-secondary pointer-events-auto rounded-lg px-1 py-1 opacity-0 transition-opacity duration-200 group-focus-within:opacity-100 group-hover:opacity-100"><svg class="icon-md-heavy" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg></button><div><table class="min-w-full" data-end="759" data-start="328"><thead data-end="403" data-start="328"><tr data-end="403" data-start="328"><th data-end="339" data-start="328">分類</th><th data-end="382" data-start="339">說明</th><th data-end="403" data-start="382">代表服務</th></tr></thead><tbody data-end="759" data-start="488"><tr data-end="554" data-start="488"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="501" data-start="488">**IaaS**</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="533" data-start="501">自己管理作業系統、網路、防火牆</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="554" data-start="533">EC2（虛擬機）</td></tr><tr data-end="625" data-start="555"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="568" data-start="555">**PaaS**</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="600" data-start="568">自動幫你處理 OS、佈署、監控等事情</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="625" data-start="600">Elastic Beanstalk</td></tr><tr data-end="696" data-start="626"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="639" data-start="626">**FaaS**</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="671" data-start="639">無伺服器架構，只有 Function 和事件觸發</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="696" data-start="671">Lambda</td></tr><tr data-end="759" data-start="697"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="713" data-start="697">**Container**</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="736" data-start="713">使用容器技術來執行應用程式，彈性、可擴展</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="759" data-start="736">ECS、EKS、App Runner</td></tr></tbody></table>

</div></div>---

## 為什麼 AWS 有這麼多種運算方式？

因為每個團隊的需求都不同：

- 有些團隊希望像過去一樣，有主機、有 OS 可以自己裝軟體（像 EC2）
- 有些人只想部署應用，不想管基礎架構（像 App Runner、Elastic Beanstalk）
- 有些系統用事件驅動設計，希望快速反應且省錢（像 Lambda）
- 有些公司習慣使用 Docker，需要容器編排平台（像 ECS、EKS）

---

## 各服務適合的情境簡介

### EC2（Amazon Elastic Compute Cloud）

- 適合需要高度自訂的作業系統、網路、軟體安裝情境
- 就像租一台雲端主機，從頭開始建系統
- 最自由，但也最需要自己管理

### Elastic Beanstalk

- 適合快速部署 Web 應用（Java、Python、Node.js…）
- 幫你處理 load balancer、autoscaling、OS patching
- 很適合快速上線產品 MVP，但自訂空間有限

### Lambda

- 適合小型服務、事件驅動邏輯（例如上傳圖片後自動轉換）
- 沒有伺服器的概念，你只要寫 function，AWS 幫你跑
- 非常適合做 event-driven 架構、backend for frontend

### ECS / EKS / App Runner

- ECS：適合熟悉容器的團隊，整合 AWS 生態系好
- EKS：Kubernetes 愛好者的最愛，管理成本略高
- App Runner：讓你用最少設定就能跑 Docker 應用，近似 Heroku 使用體驗

---

## 如何選擇？

以下是簡單的選擇指南：

<div id="bkmrk-%E4%BD%BF%E7%94%A8%E6%83%85%E5%A2%83-%E6%8E%A8%E8%96%A6%E6%9C%8D%E5%8B%99-%E8%87%AA%E5%B7%B1%E6%8E%8C%E6%8E%A7%E4%B8%80%E5%88%87%E3%80%81%E7%94%A8%E4%B9%85%E7%BF%92"><button class="hover:bg-token-main-surface-secondary text-token-text-secondary pointer-events-auto rounded-lg px-1 py-1 opacity-0 transition-opacity duration-200 group-focus-within:opacity-100 group-hover:opacity-100"><svg class="icon-md-heavy" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg></button><div><table class="min-w-full" data-end="1853" data-start="1505"><thead data-end="1551" data-start="1505"><tr data-end="1551" data-start="1505"><th data-end="1532" data-start="1505">使用情境</th><th data-end="1551" data-start="1532">推薦服務</th></tr></thead><tbody data-end="1853" data-start="1607"><tr data-end="1649" data-start="1607"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1626" data-start="1607">自己掌控一切、用久習慣了</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1649" data-start="1626">EC2</td></tr><tr data-end="1705" data-start="1650"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1671" data-start="1650">想快速部署、不管底層</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1705" data-start="1671">Elastic Beanstalk / App Runner</td></tr><tr data-end="1749" data-start="1706"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1726" data-start="1706">要做簡單的資料處理任務</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1749" data-start="1726">Lambda</td></tr><tr data-end="1800" data-start="1750"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1774" data-start="1750">已有 Docker 經驗的團隊</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1800" data-start="1774">ECS / EKS / App Runner</td></tr><tr data-end="1853" data-start="1801"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1830" data-start="1801">想玩 Kubernetes</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1853" data-start="1830">EKS</td></tr></tbody></table>

</div></div>---

## 小結

這篇文章我們簡單認識了 AWS 中的運算服務全貌，理解了不同服務的設計思路與適用場景：

- EC2 是最基礎的運算單位
- Lambda 是最極致的 serverless
- Elastic Beanstalk 與 App Runner 是簡化部署流程的幫手
- ECS / EKS 則是容器化架構的核心

---

下一篇我們將正式進入第一個 AWS 運算服務的詳細介紹 —— **EC2（虛擬主機）**，看看這個最經典、最通用的服務到底可以怎麼用。

Day 5 再見！👋

# 【AWS】Day0 AWS 系列文：從實務出發的雲端探索之旅（共 30 篇）

#### **1. AWS 簡介**

- **Day 1：認識 AWS（Amazon Web Services）**
- **Day 2：AWS 的基本知識與優勢**
- **Day 3：AWS 中的帳號、Organization 與 Project 架構（Account、OU、Tag）**

---

#### **2. AWS 中的運算服務**

- **Day 4：AWS 運算服務總覽**
- **Day 5：IaaS, PaaS, FaaS, SaaS 在 AWS 的對應與比較**
- **Day 6：EC2（虛擬機服務）**
- **Day 7：Elastic Beanstalk（簡化部署平台）**
- **Day 8：Lambda（Serverless 函式）**
- **Day 9：EKS（Elastic Kubernetes Service）**
- **Day 10：AWS App Runner &amp; ECS（Container 運行環境）**
- **Day 11：AWS 運算服務總結與選擇建議**

---

#### **3. AWS 的數據儲存服務**

- **Day 12：AWS 儲存服務總覽**
- **Day 13：S3（物件儲存服務）**
- **Day 14：RDS（關聯式資料庫）**
- **Day 15：Aurora（高效能資料庫引擎）**
- **Day 16：DynamoDB（NoSQL 資料庫）**
- **Day 17：Amazon Redshift &amp; DocumentDB &amp; ElastiCache**
- **Day 18：AWS 儲存服務總結與應用情境比較**

---

#### **4. AWS 的 API 管理服務**

- **Day 19：API 管理與整合概觀**
- **Day 20：API Gateway（全面 API 管理服務）**
- **Day 21：AppSync（GraphQL 服務）**
- **Day 22：Amazon CloudFront + Lambda@Edge（API 加速與轉換）**
- **Day 23：API 管理服務總結與實戰應用**

---

#### **5. AWS 的 Message Queue 與事件服務**

- **Day 24：SQS、SNS、EventBridge 介紹與比較**

---

#### **6. AWS 的權限與身份管理**

- **Day 25：AWS 權限系統概觀**
- **Day 26：IAM（身份與角色）**
- **Day 27：AWS Cognito &amp; IAM Identity Center（登入與使用者管理）**
- **Day 28：IAM 實作範例與最佳實踐**

---

#### **7. AWS 的帳單與費用控制**

- **Day 29：AWS 成本結構、帳單管理與費用控管（含 Cost Explorer、Budgets）**

---

#### **8. AWS 總結與應用建議**

- **Day 30：AWS 總結、常見架構與選型建議**

# 【AWS】Day 5：IaaS、PaaS、FaaS、SaaS 的差別

在進入 AWS 各項運算服務前，我們先來搞懂幾個在雲端世界中常見的術語：  
**IaaS、PaaS、FaaS、SaaS**。

這些都是「服務模型（Service Model）」的分類，代表你在使用雲端時，需要負責的事情有多少、而雲端服務商（像 AWS）又幫你負責了多少。

---

## 雲端服務模型的基本觀念

以下這張表可以幫助你快速了解四種模型的差別：

<div id="bkmrk-%E7%AE%A1%E7%90%86%E5%B0%8D%E8%B1%A1%2F%E6%A8%A1%E5%9E%8B-%E8%87%AA%E6%9E%B6%E4%B8%BB%E6%A9%9F-iaas-pa"><button class="hover:bg-token-main-surface-secondary text-token-text-secondary pointer-events-auto rounded-lg px-1 py-1 opacity-0 transition-opacity duration-200 group-focus-within:opacity-100 group-hover:opacity-100"><svg class="icon-md-heavy" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg></button><div><table class="min-w-full" data-end="993" data-start="303"><thead data-end="401" data-start="303"><tr data-end="401" data-start="303"><th data-end="313" data-start="303">管理對象/模型</th><th data-end="320" data-start="313">自架主機</th><th data-end="339" data-start="320">IaaS</th><th data-end="358" data-start="339">PaaS</th><th data-end="378" data-start="358">FaaS</th><th data-end="401" data-start="378">SaaS</th></tr></thead><tbody data-end="993" data-start="512"><tr data-end="609" data-start="512"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="525" data-start="512">硬體設備</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="535" data-start="525">✅ 自己買</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="552" data-start="535">❌ AWS 提供</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="569" data-start="552">❌ AWS 提供</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="587" data-start="569">❌ AWS 提供</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="609" data-start="587">❌ AWS 提供</td></tr><tr data-end="705" data-start="610"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="623" data-start="610">作業系統</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="633" data-start="623">✅ 自己裝</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="648" data-start="633">✅ 自己裝/管理</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="665" data-start="648">❌ AWS 管理</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="683" data-start="665">❌ AWS 管理</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="705" data-start="683">❌ AWS 管理</td></tr><tr data-end="803" data-start="706"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="725" data-start="706">中介軟體（DB、Runtime）</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="734" data-start="725">✅ 自建</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="751" data-start="734">✅ 自建</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="766" data-start="751">❌ AWS 幫你配好</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="782" data-start="766">❌ AWS 幫你配好</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="803" data-start="782">❌ AWS 幫你配好</td></tr><tr data-end="898" data-start="804"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="815" data-start="804">應用程式邏輯</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="825" data-start="815">✅ 自己寫</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="842" data-start="825">✅ 自己寫</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="859" data-start="842">✅ 自己寫</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="877" data-start="859">✅ 自己寫</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="898" data-start="877">❌ 你不需要寫</td></tr><tr data-end="993" data-start="899"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="911" data-start="899">維運、監控</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="920" data-start="911">✅ 全部自己</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="936" data-start="920">✅ 自己負責</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="953" data-start="936">⚠️ 半自動</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="970" data-start="953">❌ AWS 自動處理</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="993" data-start="970">❌ AWS 處理</td></tr></tbody></table>

</div></div>---

## 各種服務模型的說明與範例

### 1️⃣ IaaS：Infrastructure as a Service（基礎設施即服務）

- 你租用虛擬機與網路，但其他都自己來
- 最大彈性、也最麻煩，需要自己維運
- 常見服務：**Amazon EC2、VPC、EBS、ELB**

適合對象：

- 有自己建系統的需求
- 對運維有經驗、需高度自訂環境
- 例：自行部署 Apache + PHP + MySQL 的網站架構

---

### 2️⃣ PaaS：Platform as a Service（平台即服務）

- AWS 幫你準備好 OS、環境、runtime，只要上傳應用程式即可
- 不用管架設，但彈性較小
- 常見服務：**Elastic Beanstalk、App Runner**

適合對象：

- 想快速上線服務，不想煩基礎建設細節
- MVP、新創、團隊快速開發部署階段

---

### 3️⃣ FaaS：Function as a Service（函數即服務）

- 只負責一段段程式碼（function），不需要啟動伺服器
- 事件觸發執行、自動擴展、極度省錢
- 常見服務：**AWS Lambda**

適合對象：

- 事件驅動架構、簡單自動任務（如圖片轉換、排程任務）
- 想實作 Serverless 架構

---

### 4️⃣ SaaS：Software as a Service（軟體即服務）

- 使用者什麼都不用管，只要「使用」就好
- 全部由供應商管理、維運、升級
- 常見服務：**Amazon WorkMail、QuickSight、Google Workspace、Slack、Notion、Zoom**

適合對象：

- 只想「用服務」，不想管任何底層系統
- 想要穩定、成熟的產品解決問題

---

## AWS 各服務分類對照表

<div id="bkmrk-%E6%A8%A1%E5%9E%8B-aws-%E5%B8%B8%E8%A6%8B%E6%9C%8D%E5%8B%99-iaas-ec2"><button class="hover:bg-token-main-surface-secondary text-token-text-secondary pointer-events-auto rounded-lg px-1 py-1 opacity-0 transition-opacity duration-200 group-focus-within:opacity-100 group-hover:opacity-100"><svg class="icon-md-heavy" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg></button><div><table class="min-w-full" data-end="2093" data-start="1803"><thead data-end="1847" data-start="1803"><tr data-end="1847" data-start="1803"><th data-end="1808" data-start="1803">模型</th><th data-end="1847" data-start="1808">AWS 常見服務</th></tr></thead><tbody data-end="2093" data-start="1899"><tr data-end="1946" data-start="1899"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1906" data-start="1899">IaaS</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1946" data-start="1906">EC2、EBS、VPC、ELB</td></tr><tr data-end="1996" data-start="1947"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1954" data-start="1947">PaaS</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="1996" data-start="1954">Elastic Beanstalk、App Runner</td></tr><tr data-end="2046" data-start="1997"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2004" data-start="1997">FaaS</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2046" data-start="2004">AWS Lambda、Step Functions</td></tr><tr data-end="2093" data-start="2047"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2054" data-start="2047">SaaS</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2093" data-start="2054">WorkMail、QuickSight、Chime、Honeycode</td></tr></tbody></table>

</div></div>---

## 小結

透過這篇文章，我們理解了 IaaS、PaaS、FaaS、SaaS 的差異，並知道：

- 不同模型代表「管理責任」的差異
- 彈性 vs 管理負擔 是互相拉扯的
- 在 AWS 裡幾乎各種模型的服務都有對應可選

---

下一篇開始，我們就會進入實際的運算服務操作介紹 —— 先從最核心、最經典的 **EC2（Elastic Compute Cloud）虛擬機服務）** 開始探索！

我們 Day 6 再見～

# 【AWS】Day 6：運算服務 - Amazon EC2（虛擬機服務）

在前幾篇中，我們介紹了各種運算服務的分類方式，像是 IaaS、PaaS、FaaS 等等。

今天就從 AWS 中最經典、最基礎的 IaaS 服務開始介紹 —— **Amazon EC2（Elastic Compute Cloud）**

這個服務幾乎是所有 AWS 使用者早期接觸雲端的第一站，也是許多架構的基石。

---

## 什麼是 EC2？

EC2 就是一台 **雲端的虛擬機器（Virtual Machine）**。

可以想像成你在資料中心租了一台電腦，你可以選擇要用 Linux 還是 Windows，要幾核心、幾 GB 記憶體，要多大的硬碟，要不要固定 IP……這些你都可以自己決定。

建立 EC2 之後，就能連進去安裝軟體、跑應用、開 Web Server、跑排程、架設資料庫……什麼都行。

---

## EC2 的核心概念

### 1️⃣ Instance（執行個體）

每一台 EC2 就是一個 Instance，你可以開很多個、關掉它、重新啟動，甚至做 snapshot 備份。

### 2️⃣ AMI（Amazon Machine Image）

AMI 是一種映像檔，可以理解成 EC2 的「作業系統＋預設環境」模板。  
常見的 AMI 包含：

- Amazon Linux
- Ubuntu
- Debian
- Windows Server
- 自己製作的 AMI

### 3️⃣ Instance Type（機型）

AWS 提供各種 instance type 給你選擇，依照用途分類，例如：

<table id="bkmrk-%E9%A1%9E%E5%9E%8B%E4%BB%A3%E8%99%9F-%E7%94%A8%E9%80%94-%E4%BB%A3%E8%A1%A8%E5%9E%8B%E8%99%9F-t-%E7%B3%BB%E5%88%97-%E4%B8%80%E8%88%AC"><thead><tr><th>類型代號</th><th>用途</th><th>代表型號</th></tr></thead><tbody><tr><td>t 系列</td><td>一般型、低成本</td><td>t3.micro、t4g.nano</td></tr><tr><td>m 系列</td><td>平衡型</td><td>m6i.large</td></tr><tr><td>c 系列</td><td>計算密集型</td><td>c7g.medium</td></tr><tr><td>r 系列</td><td>記憶體密集型</td><td>r6g.large</td></tr><tr><td>g 系列</td><td>GPU 運算型</td><td>g5.xlarge</td></tr></tbody></table>

---

## 建立 EC2 的流程簡介

1. **選擇 AMI**：決定你要用哪一種作業系統
2. **選擇 Instance Type**：例如 `t3.micro`（免費方案支援）
3. **設定 Key Pair**：建立 SSH 金鑰，用來連線 EC2
4. **設定 Network（VPC）與 Security Group（防火牆）**
5. **設定磁碟（EBS）大小**
6. **啟動 EC2！**

完成後你會拿到一組 Public IP，就可以用 SSH 連進去使用了。

---

## 價格與計費方式

EC2 的價格組成會根據幾個項目決定：

<table id="bkmrk-%E9%A0%85%E7%9B%AE-%E8%AA%AA%E6%98%8E-instance-type-"><thead><tr><th>項目</th><th>說明</th></tr></thead><tbody><tr><td>Instance Type</td><td>不同型號每小時價格不同</td></tr><tr><td>計費模式</td><td>On-Demand / Spot / Reserved</td></tr><tr><td>使用時間</td><td>每秒或每小時計費</td></tr><tr><td>是否有附加儲存</td><td>EBS 空間也另外計費</td></tr><tr><td>傳輸流量</td><td>Outbound（出站）會收費</td></tr></tbody></table>

---

## EC2 的使用情境

EC2 適合用在哪些情況呢？以下列舉幾個常見案例：

- 需要完整控制環境與作業系統
- 要安裝自訂軟體或中介軟體（middleware）
- 部署高效能或特殊架構的應用（如容器主機、資料庫伺服器）
- 架設 CI/CD 服務、自架 Git、Redis、RabbitMQ
- 建立可手動調整、監控的後台系統

---

## 小結

EC2 是 AWS 中最基礎的運算服務，具備極大的彈性與控制權：

- 可以自由選擇作業系統與機型
- 適合習慣傳統主機操作的團隊
- 計費彈性、整合其他 AWS 服務方便

---

# EC2 進階篇：實戰操作與進階應用

在上一篇我們介紹了 EC2 的基本觀念與用途，這篇會帶大家進一步實作：

- 如何登入 EC2？
- 如何開放 port（像是 80、443）讓外部可以訪問？
- 怎麼設定開機自動執行程式？
- EC2 如何實作自動擴展（Auto Scaling）？

---

## 一、登入 EC2 實例（Linux）

當你啟動好一台 EC2 Linux 實例後，登入方式如下：

### ✅ 準備工作

- 確保當初有建立 Key Pair，下載了 `.pem` 檔案
- 開啟 Security Group 的 TCP port 22（SSH）
- 有 Public IP 或 Public DNS（可在 EC2 頁面查看）

### 💻 登入指令

```bash
chmod 400 my-key.pem
ssh -i my-key.pem ec2-user@<你的 Public IP>

```

> 不同 AMI 登入帳號不同：
> 
> - Amazon Linux 用 `ec2-user`
> - Ubuntu 用 `ubuntu`
> - Debian 用 `admin` 或 `debian`

---

## 二、開放 HTTP / HTTPS 連線

如果你要讓外部可以訪問你的 EC2，例如跑 Web server，需要設定 **Security Group**。

### ✅ 開啟常見 port：

- HTTP：80
- HTTPS：443
- 自定服務：你用什麼就開什麼（例如 3000、8080）

### 操作步驟：

1. 到 EC2 頁面點選「Security Group」
2. 找到你 EC2 所使用的 security group
3. 編輯「Inbound rules」
4. 新增：
    
    
    - 類型：HTTP、HTTPS
    - 來源：0.0.0.0/0（所有人都可以連）或限制特定 IP

---

## 三、設定開機後自動執行程式（User Data）

你可以在建立 EC2 時加入 **User Data**，當機器第一次啟動時會自動執行這段腳本。

### 範例：自動安裝 Nginx 並啟動 Web Server

```bash
#!/bin/bash
yum update -y
yum install -y nginx
systemctl enable nginx
systemctl start nginx
echo "Hello from EC2!" > /usr/share/nginx/html/index.html

```

> ✅ 注意：User Data 只能在 EC2 **首次啟動時執行一次**。  
> ✅ 如果想修改已存在的 User Data，需要重新建立新的 EC2 或自己寫開機指令。

---

## 四、設定 Auto Scaling Group（自動擴展）

Auto Scaling 是 EC2 強大的特點之一，可以根據負載自動增減 EC2 數量。

### 使用場景：

- 使用者流量增加，自動開新機器
- 流量低時自動縮減節省成本
- 保證服務有固定最少機器數（High Availability）

### 建立流程簡略：

1. 建立一個 Launch Template（定義要開什麼樣的 EC2）
2. 設定 Auto Scaling Group：
    
    
    - 定義最小 / 最大 EC2 數量
    - 設定 Scaling Policy（例如 CPU &gt; 60% 時新增 1 台）
3. 可結合 Load Balancer，讓所有流量平均分配到所有 EC2 上

---

## 五、備份與還原

EC2 本身不會自動備份，要使用以下方式保護資料：

### 快照（Snapshot）

- 可對 EBS 磁碟建立快照
- 快照可以還原成新的 EBS 磁碟掛回 EC2

### 建立 AMI

- 對目前 EC2 建立 AMI 映像檔
- 可隨時用這個 AMI 快速建立新機器（帶所有設定）

---

## 小結

這篇進階實作帶你實際操作 EC2，包括：

- 使用 SSH 登入實例
- 開放外部連線 port
- 利用 User Data 自動化設定
- 設定 Auto Scaling Group 自動擴展
- 建立快照與 AMI 做備份

EC2 的強大在於它的彈性與可控性，從個人專案到大型分散式系統，都能發揮作用。

---

下一篇，我們將來看看如果你不想自己處理作業系統、網路設定、磁碟掛載這些瑣事時，AWS 有沒有更輕鬆的方案？  
**Day 7：Elastic Beanstalk - 全自動部署的神隊友！**，我們不見不散！

# 【AWS】Day 7：運算服務 - Elastic Beanstalk（自動化部署服務）

在前幾篇，我們介紹了 EC2，了解了怎麼自己開一台雲端主機、自己設定環境、自己管防火牆、自己擴展、自己備份。

但如果你覺得自己管這些基礎建設很麻煩，希望可以更專注在「寫程式」跟「部署應用程式」上面，那 AWS 其實也有更方便的選擇：  
今天要介紹的就是 —— **Elastic Beanstalk**

---

## 什麼是 Elastic Beanstalk？

Elastic Beanstalk 是 AWS 提供的一種 **PaaS（平台即服務）**。

它的目標很簡單：

- 你只要準備好應用程式
- 把應用程式丟給 Beanstalk
- Beanstalk 幫你處理所有底層繁瑣的事情

包括：

- 建好 EC2 instance
- 建好負載平衡器（Load Balancer）
- 建好 Auto Scaling
- 建好 Security Group、防火牆設定
- 幫你佈署、監控、收集日誌
- 還可以一鍵回滾版本

你專注在開發跟部署，AWS 幫你顧好其他基礎設施。

---

## 支援的應用類型

Elastic Beanstalk 支援超多種語言與平台：

- Node.js
- Python
- Java
- Ruby
- PHP
- .NET
- Go
- Docker（完整支援 Container）
- 靜態網站（HTML/CSS/JS）

如果你是 Web 應用開發者，基本上都可以直接使用 Beanstalk 部署。

---

## Elastic Beanstalk 的架構概念

雖然 Beanstalk 幫你管理底層資源，但其實背後還是會幫你建立 AWS 其他服務，包括：

- EC2（跑你的應用）
- Elastic Load Balancer（分流）
- Auto Scaling Group（自動擴展）
- CloudWatch（監控、日誌）
- RDS（如果有需要資料庫）

這些資源都是 Beanstalk 幫你「托管」，你可以選擇要不要深入調整。

> ✅ 你仍然可以進入 EC2 頁面看到自己應用用到的實例，但建議不要直接手動修改，避免破壞 Beanstalk 的管理邏輯。

---

## 建立 Elastic Beanstalk 應用的流程

1. 進入 AWS Console，打開 Elastic Beanstalk
2. 點選「Create Application」
3. 填入：
    
    
    - Application Name（應用名稱）
    - Platform（選語言與環境）
    - Upload your code（上傳程式碼壓縮檔）
4. 設定部署設定（可以預設）
5. 點下「Create Application」

幾分鐘後，AWS 就會幫你建好整個環境，還會提供一個 URL，直接可以訪問你的應用！

---

## 部署更新與版本管理

Elastic Beanstalk 很貼心地內建了「版本管理」功能：

- 每次部署新程式碼，都會建立一個新的版本
- 可以快速回滾（Rollback）到前一個版本
- 可以在不同環境（例如 dev、prod）快速切換程式版本

---

## 常見 Elastic Beanstalk 的使用情境

- 快速啟動 MVP、小型專案
- 開發階段測試不同版本部署
- 中小型團隊的 Web 應用架設
- 需要簡單 Auto Scaling、Load Balancing 但不想自己架設

---

## 注意事項

- **不是完全免維運**：雖然簡化很多，但還是要稍微理解 VPC、Security Group 等 AWS 基本概念
- **資源會產生費用**：Beanstalk 本身不收費，但底層建立的 EC2、RDS、S3 等都會收費
- **適合標準架構**：如果應用有非常特殊的環境需求（例如自訂作業系統 kernel）可能還是要回去用 EC2 自己建

---

## 小結

Elastic Beanstalk 是 AWS 上手最簡單的運算服務之一：

- 幫你管理底層基礎建設
- 讓你快速部署、快速擴展、快速回滾
- 適合中小型 Web 應用，也適合剛開始學 AWS 的人嘗試

當然可以！這裡直接幫你補上  
**Elastic Beanstalk 簡易實作教學（以 Node.js 舉例）**，延續前面文章的語氣與結構，實作內容也保持簡單明瞭，讓讀者可以直接跟著做。

---

# Elastic Beanstalk 簡易實作教學（以 Node.js 範例）

剛剛介紹了 Elastic Beanstalk 是什麼，接下來就實際帶大家操作一次：

- 我們會快速做出一個 Node.js 小應用
- 壓成 ZIP 檔
- 部署到 Elastic Beanstalk 上

目標是：**10 分鐘內完成一個雲端 Web App！**

---

## 1. 建立一個簡單的 Node.js 專案

首先，在你的電腦上開一個新資料夾，例如 `eb-demo-app`，然後建立最基本的 Node.js 應用。

```bash
mkdir eb-demo-app
cd eb-demo-app
npm init -y
npm install express

```

接著建立一個 `app.js`，內容如下：

```javascript
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;

app.get('/', (req, res) => {
  res.send('Hello from Elastic Beanstalk!');
});

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

```

這樣就完成了最基本的 Node.js Web 應用。

注意：這裡我們用 `process.env.PORT` 是 Elastic Beanstalk 的部署要求，讓它自動分配正確的 port。

---

## 2. 建立必要的設定檔

Elastic Beanstalk 在 Node.js 環境中，需要有一個叫做 `package.json` 的設定檔（剛剛 `npm init` 已經自動生成），還需要明確標出啟動指令。

請確認 `package.json` 中有這一段：

```json
"scripts": {
  "start": "node app.js"
}

```

如果沒有，手動加上去，這樣 Elastic Beanstalk 才知道要怎麼啟動你的應用程式。

---

## 3. 壓縮成 ZIP 檔案

回到 `eb-demo-app` 目錄，將所有檔案壓縮成一個 `.zip` 檔。

注意：**不要把整個資料夾壓縮進去！**

- ZIP 檔裡面應該是直接包含 `app.js`、`package.json`，不是包一層資料夾！

例如正確的內容：

```
app.js
package.json
node_modules/

```

如果你想省空間，其實可以只壓 `app.js` 和 `package.json`，讓 Elastic Beanstalk 自己在雲端跑 `npm install`。

---

## 4. 登入 AWS Elastic Beanstalk 控制台

打開 [Elastic Beanstalk 主控台](https://console.aws.amazon.com/elasticbeanstalk)

1. 點選「Create Application」
2. 填上：
    
    
    - **Application name**：例如 `demo-node-app`
    - **Platform**：選 `Node.js`
    - **Application code**：選擇「Upload your code」上傳剛剛的 `.zip` 檔
3. 點選「Create Application」

接下來 AWS 會自動：

- 建 EC2
- 建 Load Balancer
- 建 Auto Scaling Group
- 佈署你的應用程式

大概 3-5 分鐘後，就可以從 Elastic Beanstalk 頁面拿到一個網址，直接訪問你的應用！

---

## 5. 驗證部署結果

打開 Elastic Beanstalk 頁面給你的 URL，比如：

```
http://demo-node-app.ap-northeast-1.elasticbeanstalk.com/

```

你應該會看到網頁上寫著：

```
Hello from Elastic Beanstalk!

```

部署成功 🎉！

---

## 小結

這就是最簡單的 Elastic Beanstalk 實作流程，總結一下步驟：

1. 建立應用（Node.js / Python / etc）
2. 加好 `start` 指令
3. 壓成 ZIP 檔
4. 上傳到 Elastic Beanstalk
5. 幾分鐘內自動建立並上線

對於想快速上線 MVP 或測試環境的人來說，Elastic Beanstalk 真的是一個超省力的好選擇！

---

沒問題！這裡幫你補上  
**Elastic Beanstalk 簡易實作教學（Python / Flask 範例）**  
延續剛剛 Node.js 範例的風格和節奏，一樣清楚、快速能跟著做！

---

# Elastic Beanstalk 簡易實作教學（以 Python / Flask 範例）

如果你是使用 **Python** 的開發者，想要快速把 Flask App 部署到 Elastic Beanstalk，也是非常簡單的！

這篇教學會帶你從零建立一個最簡單的 Flask App，並且成功部署到 Elastic Beanstalk 上運行。

---

## 1. 建立一個 Flask 專案

首先，在你的電腦上新建一個資料夾，例如 `eb-python-demo`：

```bash
mkdir eb-python-demo
cd eb-python-demo
python3 -m venv venv
source venv/bin/activate  # Windows 用 venv\Scripts\activate
pip install flask

```

接著新增一個 `application.py`（注意：**檔名是 `application.py`，Elastic Beanstalk 預設會找這個名字**）

```python
from flask import Flask

application = Flask(__name__)

@application.route("/")
def hello():
    return "Hello from Elastic Beanstalk with Python!"

if __name__ == "__main__":
    application.run(host='0.0.0.0', port=5000)

```

這樣就完成一個超簡單的 Flask Web App！

---

## 2. 建立 requirements.txt

Elastic Beanstalk 需要知道要安裝哪些套件，所以要建立一個 `requirements.txt` 檔案，內容如下：

```
Flask

```

（未來如果有更多套件，就加在這個檔案裡。）

指令快速產生：

```bash
pip freeze > requirements.txt

```

---

## 3. 建立 `.ebextensions` 設定檔（可選）

如果你想在 Elastic Beanstalk 上做更多進階設定（例如自動安裝系統套件），可以用 `.ebextensions`。  
不過這次我們簡單部署，這步可以先跳過。

---

## 4. 壓縮成 ZIP 檔案

跟剛剛 Node.js 一樣，把你的檔案壓縮成一個 `.zip` 檔案。

⚡ **注意**：

- ZIP 裡面應該包含：`application.py`、`requirements.txt`
- 不要壓整個資料夾進去

正確的結構是：

```
application.py
requirements.txt

```

---

## 5. 部署到 Elastic Beanstalk

打開 [AWS Elastic Beanstalk Console](https://console.aws.amazon.com/elasticbeanstalk)

操作步驟：

1. 點選「Create Application」
2. 填寫：
    
    
    - **Application name**：例如 `demo-python-app`
    - **Platform**：選 **Python**
    - **Platform Branch**：例如 Python 3.11
3. 上傳剛剛壓好的 `.zip` 檔案
4. 點選「Create Application」

Elastic Beanstalk 會自動：

- 幫你建立 EC2
- 幫你佈署 Flask
- 幫你設定 Load Balancer

大約 3-5 分鐘部署完成。

---

## 6. 驗證部署結果

部署好之後，Elastic Beanstalk 會給你一個 URL，比如：

```
http://demo-python-app.ap-northeast-1.elasticbeanstalk.com/

```

打開之後應該會看到：

```
Hello from Elastic Beanstalk with Python!

```

大成功！🎉

---

## 小結

Python / Flask 的 Elastic Beanstalk 快速部署流程：

1. 建好 Flask 應用（注意 `application.py`）
2. 建立 `requirements.txt`
3. 打包成 `.zip`
4. 上傳到 Elastic Beanstalk
5. 等待幾分鐘，直接有一個可以訪問的 Web App

---

# Elastic Beanstalk 進階教學

（環境變數、eb CLI 部署、自動串接 RDS）

---

## 一、設定環境變數（Environment Variables）

在實際應用中，很常需要讓應用程式讀取一些敏感資訊（像資料庫連線字串、API 金鑰等等），這些不適合硬寫在程式碼裡。  
這時就需要使用「環境變數」。

### ✅ 在 Elastic Beanstalk 上設定環境變數

操作步驟：

1. 打開 Elastic Beanstalk Console，進入你的應用 Environment
2. 左邊選單找到「Configuration」
3. 找到「Software」，按下「Edit」
4. 在 Environment properties 區塊新增你的變數，例如：

<table id="bkmrk-key-value-db_host-my"><thead><tr><th>Key</th><th>Value</th></tr></thead><tbody><tr><td>`DB_HOST`</td><td>mydb.xxxxxxxxx.rds.amazonaws.com</td></tr><tr><td>`DB_USER`</td><td>admin</td></tr><tr><td>`DB_PASSWORD`</td><td>supersecure123</td></tr></tbody></table>

儲存後，Elastic Beanstalk 會自動重啟環境，讓你的應用可以透過環境變數讀取設定。

---

### ✅ 在程式中讀取環境變數（Python Flask 範例）

```python
import os

db_host = os.getenv('DB_HOST')
db_user = os.getenv('DB_USER')
db_password = os.getenv('DB_PASSWORD')

```

這樣就能安全地使用環境設定，不用把敏感資訊寫死在程式碼裡！

---

## 二、使用 eb CLI 進行快速部署

如果你覺得每次都開 Console 點來點去很煩，AWS 提供了專門給 Elastic Beanstalk 用的指令工具：  
**Elastic Beanstalk CLI (eb CLI)**

可以直接從 Terminal 完成初始化、部署、查看環境狀態等等，非常方便！

### ✅ 安裝 eb CLI

先安裝 AWS EB CLI：

```bash
pip install awsebcli --upgrade

```

> 有時候 MacOS 或 Linux 需要加上 `--user`，或用 virtualenv 安裝。

安裝好後，試試：

```bash
eb --version

```

如果能正常顯示版本號，代表安裝成功。

---

### ✅ eb CLI 操作基本流程

1. 登入 AWS（要先設定好 AWS CLI credentials）

```bash
aws configure

```

2. 初始化 eb 專案

在你的應用資料夾內：

```bash
eb init

```

過程中會問你：

- 要使用哪個 Region？
- 是哪個 Platform？（Node.js、Python 等）
- 是否需要設定 SSH？

3. 建立 Environment

```bash
eb create dev-env

```

（可以自己取環境名字）

4. 部署更新

```bash
eb deploy

```

5. 查看環境資訊

```bash
eb status

```

6. 開啟瀏覽器查看應用

```bash
eb open

```

超級方便，可以完全不開網頁直接部署更新！

---

## 三、自動串接 RDS 資料庫（一起建立）

如果你的應用需要資料庫（例如 MySQL、PostgreSQL），Elastic Beanstalk 可以在建立 Environment 時**順便幫你建立 RDS**！

### ✅ 如何設定

1. 建立 Environment 時，選擇「Configure more options」
2. 找到「Database」選項，點選「Edit」
3. 選擇：
    
    
    - 資料庫引擎（MySQL、PostgreSQL）
    - 資料庫版本
    - 使用者名稱、密碼
    - 硬碟空間大小

建立好後：

- Elastic Beanstalk 會自動把 RDS 與你的應用放在同一個 VPC、Security Group
- 會自動把資料庫連線資訊存到環境變數（`RDS_HOSTNAME`、`RDS_USERNAME`、`RDS_PASSWORD`、`RDS_PORT`）

你的 Flask / Node.js 應用只要讀取這些環境變數，就能連上資料庫！

---

### ✅ Flask 範例（連接 Elastic Beanstalk RDS）

```python
import os
import pymysql

db_host = os.getenv('RDS_HOSTNAME')
db_user = os.getenv('RDS_USERNAME')
db_password = os.getenv('RDS_PASSWORD')
db_name = os.getenv('RDS_DB_NAME')

conn = pymysql.connect(
    host=db_host,
    user=db_user,
    password=db_password,
    database=db_name
)

```

就可以在應用程式中直接操作資料庫啦！

---

## 小結

Elastic Beanstalk 除了快速部署應用，進階玩法還可以：

- 設定環境變數，安全管理敏感資訊
- 使用 eb CLI，極速 Terminal 部署
- 一起建立 RDS，讓應用直接串接雲端資料庫

---

# Elastic Beanstalk 常見錯誤排除指南

Elastic Beanstalk 幫我們自動化了很多事，但實際使用過程中還是很容易遇到一些常見錯誤。

這篇幫大家整理了：

- 常見的 Elastic Beanstalk 部署問題
- 原因說明
- 解決方法

給你一份遇到問題時能快速排雷的工具包！

---

## 1. 部署後出現 502 Bad Gateway

### 問題現象

- 部署成功，但一打開應用的網址就跳出 502 Bad Gateway
- 或者頁面載入超級慢然後 timeout

### 常見原因

- **應用程式沒有正常啟動**
- **聽錯了 Port**（Elastic Beanstalk 預設會轉送到 80 或由 proxy pass）

### 解決方法

✅ 檢查你的應用程式是否有監聽正確的 Port：

- Node.js / Flask / Django 等應用，要監聽環境變數給定的 port
- 例如 Flask：

<div id="bkmrk-python-%E8%A4%87%E8%A3%BD%E7%B7%A8%E8%BC%AF-applicat"><div>python</div><div><div><div><span class="" data-state="closed"><button aria-label="複製" class="flex gap-1 items-center select-none px-4 py-1"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>複製</button></span><span class="" data-state="closed"><button class="flex items-center gap-1 px-4 py-1 select-none"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>編輯</button></span></div></div></div><div>`application.run(host=<span class="hljs-string">'0.0.0.0'</span>, port=<span class="hljs-built_in">int</span>(os.environ.get(<span class="hljs-string">'PORT'</span>, <span class="hljs-number">5000</span>)))`</div></div>✅ 查看 Logs：

- Elastic Beanstalk Console → Logs → Request logs → Last 100 lines
- 查看應用啟動過程是否有錯誤訊息，例如 module not found、port 綁定錯誤等

---

## 2. 部署失敗，顯示「Instance deployment failed」

### 問題現象

- 上傳 ZIP 包後，環境 Health 直接變紅色
- console 顯示 deployment failed

### 常見原因

- 上傳的 ZIP 包結構錯誤
- 必要檔案（像 `application.py` 或 `package.json`）缺少或錯誤
- requirements.txt 有誤（例如版本衝突、缺失）

### 解決方法

✅ 確認 ZIP 檔內部是正確的結構：

<div id="bkmrk-%E8%A4%87%E8%A3%BD%E7%B7%A8%E8%BC%AF-application.py-"><div></div><div><div><div><span class="" data-state="closed"><button aria-label="複製" class="flex gap-1 items-center select-none px-4 py-1"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>複製</button></span><span class="" data-state="closed"><button class="flex items-center gap-1 px-4 py-1 select-none"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>編輯</button></span></div></div></div><div>`application.pyrequirements.txt（不要包一層資料夾）`</div></div>✅ 如果是 Python，確保有 `requirements.txt`，且內容正確。

✅ 部署失敗可以看 Instance logs：

- Console → Logs → Instance logs → 全部下載下來
- 查看 `/var/log/eb-engine.log` 或 `/var/log/web.stdout.log` 裡的錯誤訊息

---

## 3. 無法 SSH 連線到 EC2

### 問題現象

- 按下「Connect」或者手動 SSH 連線失敗
- 卡在 timeout，無法進去 Instance

### 常見原因

- Security Group 沒有開放 port 22
- 沒有設定 Key Pair
- 錯誤使用 Public IP 或 DNS 名稱

### 解決方法

✅ 確認 Environment → Configuration → Security → EC2 Key Pair 有設定。

✅ Security Group 要開 TCP 22 port，允許你的 IP 連進去。

✅ 連線指令正確，例如：

<div id="bkmrk-bash-%E8%A4%87%E8%A3%BD%E7%B7%A8%E8%BC%AF-ssh--i-my-"><div>bash</div><div><div><div><span class="" data-state="closed"><button aria-label="複製" class="flex gap-1 items-center select-none px-4 py-1"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>複製</button></span><span class="" data-state="closed"><button class="flex items-center gap-1 px-4 py-1 select-none"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>編輯</button></span></div></div></div><div>`ssh -i my-key.pem ec2-user@your-public-dns.amazonaws.com`</div></div>---

## 4. 資源遺留：環境刪掉但 EC2、RDS 還在

### 問題現象

- 刪除 Elastic Beanstalk 環境後
- 發現 EC2、RDS、Security Group、EBS Volume 還殘留在帳號中

### 常見原因

- 預設刪除環境時，只會刪除 Beanstalk 直接管理的資源
- 外掛式 RDS、手動建立的資源不會跟著刪

### 解決方法

✅ 手動到 EC2、RDS、VPC 頁面，檢查並刪除殘留的資源。

✅ 未來建立環境時，如果有開 RDS，**選「內嵌式 RDS」**（跟環境生命週期綁在一起），才會自動清掉。

---

## 5. 更新應用程式後，環境變紅，回不去

### 問題現象

- 部署新版本應用後
- Elastic Beanstalk 環境變成 Severe（紅色）
- 想緊急回滾但不知道怎麼做

### 常見原因

- 新版程式出錯，導致服務無法正常啟動
- Elastic Beanstalk 的 Health Check 判定失敗

### 解決方法

✅ 使用 Elastic Beanstalk 的 **版本回滾功能**：

- Console → Application → Versions
- 找到上一個成功部署的版本，按「Deploy」

✅ 部署失敗時，通常 Console 會自動記錄上一次成功的狀態，可以快速 Rollback。

---

## 6. 頻繁超過資源限制（Instance CPU 爆表、Memory 爆滿）

### 問題現象

- Elastic Beanstalk 環境 Health 不穩
- 應用載入變慢或直接掛掉

### 常見原因

- Instance 選太小（例如 t2.micro 記憶體太小）
- 程式設計有記憶體洩漏（Memory Leak）
- 同一台機器上負載過高

### 解決方法

✅ 調整 Instance Type：

- 將 EC2 Instance 換成 t3.medium、t3.large 或更高階型號
- Elastic Beanstalk 支援一鍵調整 Scaling 設定

✅ 設定 Auto Scaling Policy：

- 當 CPU &gt; 60% 自動擴展一台
- 當 CPU &lt; 30% 自動縮減

✅ 用 CloudWatch 監控 CPU、Memory 使用率，設通知（Alarm）

---

# 小結

Elastic Beanstalk 雖然大幅降低了管理負擔，但遇到問題時：

- **善用 Logs**
- **善用 Environment Health Status**
- **善用版本控制回滾機制**

這三個是最快速排除問題的關鍵！

---

下一篇，我們將進入 AWS 的 **Serverless 運算世界 —— Lambda！**  
看看如果連 EC2、Load Balancer 都不想管，只想「寫 function」，AWS 能怎麼幫你做到！

Day 8 再見 👋

# 【AWS】【Elastic Beanstalk】 常見部署結構推薦指南&費用最佳化建議

# Elastic Beanstalk 常見部署結構推薦指南

Elastic Beanstalk 很方便，但如果搭配得好，會讓你的應用又穩又省錢；搭配得不好，可能會出現：

- 資源不足導致當機
- 成本過高
- 擴展緩慢等問題

這篇針對不同情境，推薦最佳部署結構與選型建議。

---

## 1. 小型網站 / MVP 初版

### 適用情境

- 一般公司網站
- 初版產品 Demo
- 內部管理系統
- 低流量 Web API

### 推薦設定

<table id="bkmrk-%E9%A0%85%E7%9B%AE-%E5%BB%BA%E8%AD%B0%E5%80%BC-instance-type"><thead><tr><th>項目</th><th>建議值</th></tr></thead><tbody><tr><td>Instance Type</td><td>`t3.micro` 或 `t3.small`（免費額度或低價）</td></tr><tr><td>Instance 數量</td><td>最小 1 台，最大 1 台（不開 Auto Scaling）</td></tr><tr><td>Database</td><td>內嵌式 RDS 或直接外接小型 DB（如 Lightsail）</td></tr><tr><td>Load Balancer</td><td>不使用（單台直接對外即可）</td></tr><tr><td>部署方式</td><td>單一環境，使用 Rolling 更新</td></tr></tbody></table>

### 原因分析

- 成本極低（每月不到 10 美金）
- 流量低，單台就夠
- 部署簡單，維運容易
- 適合快速迭代、驗證市場

---

## 2. 中型後端服務 / API Server

### 適用情境

- 中等流量的 Web API
- 手機 App 的後端
- 需要支援突發高峰流量

### 推薦設定

<table id="bkmrk-%E9%A0%85%E7%9B%AE-%E5%BB%BA%E8%AD%B0%E5%80%BC-instance-type-0"><thead><tr><th>項目</th><th>建議值</th></tr></thead><tbody><tr><td>Instance Type</td><td>`t3.medium` 或 `t3.large`</td></tr><tr><td>Instance 數量</td><td>最小 2 台，最大 4 台（開啟 Auto Scaling）</td></tr><tr><td>Database</td><td>外接獨立 RDS（例如 db.t3.medium）</td></tr><tr><td>Load Balancer</td><td>啟用 Application Load Balancer (ALB)</td></tr><tr><td>部署方式</td><td>Rolling with additional batch（保證不中斷）</td></tr></tbody></table>

### 原因分析

- 2 台機器可避免單點故障
- Auto Scaling 可以因應突發性流量（例如促銷活動）
- 獨立 RDS 提高資料庫效能與可管理性
- ALB 可支援 HTTPS 與多目標健康檢查

---

## 3. 高流量大型網站 / 高併發應用

### 適用情境

- 電商網站
- 多人即時服務（如聊天室、直播）
- 高 API 併發應用

### 推薦設定

<table id="bkmrk-%E9%A0%85%E7%9B%AE-%E5%BB%BA%E8%AD%B0%E5%80%BC-instance-type-1"><thead><tr><th>項目</th><th>建議值</th></tr></thead><tbody><tr><td>Instance Type</td><td>`m6i.large` 或 `c6g.large`（效能型）</td></tr><tr><td>Instance 數量</td><td>最小 4 台，最大 10 台（Auto Scaling）</td></tr><tr><td>Database</td><td>專屬大型 RDS（或 Aurora Serverless）</td></tr><tr><td>Load Balancer</td><td>Application Load Balancer（或加上 CloudFront）</td></tr><tr><td>部署方式</td><td>Immutable deployment（零中斷更新）</td></tr></tbody></table>

### 原因分析

- 使用效能型機器降低單位併發成本
- 多台擴展配合 Auto Scaling 可應付大幅波動
- Immutable 部署方式可避免更新失敗影響現有線上服務
- Load Balancer 可搭配 CDN 提升全球加速

---

## 4. Serverless + Beanstalk 混合模式（Hybrid）

### 適用情境

- 需要兼顧複雜計算與 Serverless 架構
- 某些任務使用 Lambda 執行（如影像處理、寄信）
- 主系統仍以 Beanstalk EC2 部署

### 推薦設定

- 核心 API / Web server 放在 Elastic Beanstalk
- 背景工作（cron job、queue consumer）用 AWS Lambda + EventBridge
- File upload / Static Content 用 S3 + CloudFront
- Serverless 服務與 EC2 協作，共用 IAM role / VPC

### 原因分析

- 彈性最佳化：主服務 EC2 高效能、子任務 Lambda 靈活啟動
- 成本控制佳，避免小工作佔用大型主機資源
- 容錯能力提升，Lambda 失敗也不影響主要 API

---

# 小結

不同規模、不同需求下，Elastic Beanstalk 最佳部署建議：

<table id="bkmrk-%E8%A6%8F%E6%A8%A1-instance%E5%BB%BA%E8%AD%B0-auto-s"><thead><tr><th>規模</th><th>Instance建議</th><th>Auto Scaling</th><th>Load Balancer</th><th>Database建議</th></tr></thead><tbody><tr><td>小型</td><td>1台小機器</td><td>❌</td><td>❌</td><td>內嵌 RDS 或外接小型 DB</td></tr><tr><td>中型</td><td>2-4台中型機器</td><td>✅</td><td>✅</td><td>獨立 RDS</td></tr><tr><td>大型</td><td>4-10台高效能機器</td><td>✅</td><td>✅（加 CDN）</td><td>大型 RDS 或 Aurora Serverless</td></tr><tr><td>混合</td><td>Beanstalk + Lambda</td><td>✅</td><td>✅</td><td>跨服務組合</td></tr></tbody></table>

Elastic Beanstalk 的強大就在於  
**從個人開發到企業級系統，都可以靈活搭配出適合自己的方案！**

---

# Elastic Beanstalk 費用最佳化建議

（含 Auto Scaling 節省技巧）

Elastic Beanstalk 本身不收管理費，**真正產生費用的是底層的 EC2、Load Balancer、RDS、S3、傳輸流量**等資源。

所以如果想讓 Beanstalk 部署既穩定又省錢，關鍵在於：

- 控制 EC2 成本
- 適當調整擴展策略
- 避免無意義的浪費

這篇就帶你掌握 Elastic Beanstalk 最有效的省錢方法！

---

## 1. 使用 Reserved Instance（RI）

### ✅ 做法

如果你的應用是「穩定長期運行」（例如公司網站、後端 API），  
建議購買 **Reserved Instances**（RI）來取代 On-Demand。

- 可省下 **最高 72%** 成本（依付款方案而定）
- 1 年期或 3 年期，分為 All Upfront / Partial Upfront / No Upfront 付款選項

> 📌 注意：RI 是鎖定在「Region + Instance Type family」（例如 t3 family）

### ✅ 適用時機

- 流量穩定的長期應用
- 預期至少持續使用 1 年以上

---

## 2. 配合 Auto Scaling 使用 Spot Instances

### ✅ 做法

Elastic Beanstalk 的 Auto Scaling Group 可以設定混合策略：

- 混合使用 On-Demand 與 Spot Instances
- 例如：
    
    
    - 30% On-Demand 保底
    - 70% Spot 便宜又彈性擴展

在環境設定裡選擇：

> Capacity → Auto Scaling → Instances → Purchase Options → "Combine purchase options and instance types"

這樣可以極大降低「額外擴展台數」的費用。

Spot 便宜很多（常常是 On-Demand 價格的 10%~30%），超適合做大量讀取、彈性擴展的情境！

### ✅ 適用時機

- 流量不穩定、會爆量但可以短期容忍失敗（如直播、促銷活動）
- 不要求 100% 可用性的非關鍵服務（如統計、備援服務）

---

## 3. 適當設定 Auto Scaling 門檻

Auto Scaling 是節省資源的重要機制，但如果設定不當，反而可能讓你爆量開機器！

### ✅ 推薦設定策略

- **Scaling Up（擴展）**：CPU 使用率 &gt; 60% 才新增機器
- **Scaling Down（縮減）**：CPU 使用率 &lt; 30% 才關閉機器
- **Cooldown Time**（冷卻時間）：
    
    
    - 擴展冷卻時間：180 秒
    - 縮減冷卻時間：300 秒

這樣可以避免瞬間小波動就導致無謂的開關機，  
減少 Auto Scaling 的震盪效應（flapping）。

---

## 4. 選擇適合的 Instance Type

不要一開始就開很大的機器，建議從小型機型開始測試：

- 小型 / 中型服務：選 `t3.medium` 或 `t4g.medium`（ARM 架構更便宜）
- 記憶體吃重服務：選 `r6g.large`
- 計算密集服務：選 `c6g.large`

**t 系列機型**特別便宜，還有 **CPU Credit** 機制，非常適合大部分輕中量應用。

> 📌 如果有資格使用 AWS Savings Plan，搭配起來成本又能再下降！

---

## 5. 適時關閉閒置資源

Elastic Beanstalk 的環境如果沒有在使用，底層資源（EC2、EBS、ALB）還是會一直產生費用！

### ✅ 建議

- 測試環境（staging / dev）用完記得直接 **terminate**。
- 用 Lifecycle Policy 自動清除超過 30 天未使用的 Application Versions。
- RDS 如果是測試用，也記得關掉（RDS 比 EC2 還貴！）。

---

## 6. 用 CloudFront 做流量加速，減少 ALB 費用

如果你的 Elastic Beanstalk 是做靜態檔案 / API Server，  
流量高峰期可以前面加一層 **CloudFront CDN**，減少直接打到 Load Balancer 的次數。

- 減少 ALB Request 數量 ➔ 降低 ALB 成本
- 加速全球存取速度
- 保護背後應用不被 DDoS 打爆（搭配 AWS Shield）

---

# 小結

Elastic Beanstalk 節省費用重點整理：

<table id="bkmrk-%E6%96%B9%E6%B3%95-%E6%A0%B8%E5%BF%83%E6%95%88%E6%9E%9C-reserved-ins"><thead><tr><th>方法</th><th>核心效果</th></tr></thead><tbody><tr><td>Reserved Instances</td><td>穩定運作環境省錢最大化（&gt;50%）</td></tr><tr><td>Spot Instances混搭</td><td>彈性負載環境大幅降低成本</td></tr><tr><td>正確設 Auto Scaling</td><td>避免不必要的機器啟動/關閉</td></tr><tr><td>選對 Instance Type</td><td>減少過度規格浪費</td></tr><tr><td>定期清理閒置資源</td><td>減少測試環境與多餘資源費用</td></tr><tr><td>加上 CloudFront</td><td>減少 ALB 成本與提高存取效率</td></tr></tbody></table>

Elastic Beanstalk 不只是簡單，  
只要稍微搭配得好，也能做到「低成本、高可靠、高效能」的部署！

---

# 【AWS】Day 8：運算服務 - AWS Lambda（Serverless 運算服務）

從前幾篇我們一路看了 EC2（自己管主機）、Elastic Beanstalk（半自動化部署）。

但如果你希望：

- 完全不用開伺服器
- 完全不用自己處理機器、網路、Scaling
- 只要專心「寫功能」就好

那 AWS Lambda 正是為了這樣的世界而生的！

今天正式進入 AWS Serverless 世界 —— **Lambda**！

---

## 什麼是 AWS Lambda？

**AWS Lambda** 是一個「事件驅動」的計算服務。

你只需要撰寫小段的**程式碼 Function**，告訴 AWS：

- 什麼時候要執行（例如 API 呼叫、排程時間、檔案上傳）
- 要執行什麼邏輯（你的程式碼）
- 設定好記憶體、超時時間

剩下的：

- 執行環境（OS、Patch、Security）
- 擴展（Scaling up &amp; down）
- 負載均衡
- 日誌收集
- 資源回收

全部 AWS 幫你自動處理。

你只要負責寫功能，其他都不用煩惱！

---

## Lambda 的基本特性

<table id="bkmrk-%E9%A0%85%E7%9B%AE-%E8%AA%AA%E6%98%8E-%E9%81%8B%E7%AE%97%E5%96%AE%E4%BD%8D-function%EF%BC%88"><thead><tr><th>項目</th><th>說明</th></tr></thead><tbody><tr><td>運算單位</td><td>Function（函數）</td></tr><tr><td>部署方式</td><td>上傳程式碼（ZIP）或直接在 Console 編輯</td></tr><tr><td>支援語言</td><td>Node.js, Python, Java, Go, .NET, Ruby…</td></tr><tr><td>記憶體設定</td><td>128MB ~ 10GB（每64MB為單位）</td></tr><tr><td>執行時間限制</td><td>最長 15 分鐘</td></tr><tr><td>價格模式</td><td>依執行次數 + 記憶體用量 + 執行時間計價</td></tr><tr><td>自動擴展</td><td>是（幾乎無上限，橫向自動擴展）</td></tr></tbody></table>

---

## Lambda 的典型使用情境

- API Gateway ➔ Lambda ➔ 回應 Web API
- S3 上傳檔案 ➔ Lambda 處理轉檔（圖片縮圖、影片轉檔）
- DynamoDB 資料異動 ➔ Lambda 處理後續邏輯
- 排程任務（類似 crontab） ➔ 用 EventBridge 觸發 Lambda
- 輕量型後端服務（例如 Form submit、小型 Game Server）

---

## Lambda 的基本運作流程

以「API Server」為例：

1. 使用 **API Gateway** 建立 API Endpoint
2. 當使用者呼叫 API 時，API Gateway 觸發 Lambda
3. Lambda 執行你的程式邏輯（例如查資料庫、處理資料）
4. Lambda 執行完畢，把結果回傳給 API Gateway，再傳給使用者

整個過程中：

- 沒有 EC2
- 沒有 Load Balancer
- 沒有固定伺服器
- 自動擴展，不需要自己設定台數

---

## Lambda 的計費方式

Lambda 的收費方式非常彈性，也很適合小量或突發性使用的場景。

計費公式：

> **總費用 = 呼叫次數 +（記憶體用量 × 執行時間）**

詳細來說：

- **每個月前 1M 次請求免費**
- **每個月 400,000 GB-seconds 免費**
- 超出後，依照實際用量超便宜的單位收費

簡單說就是：

- 程式小、執行快，超便宜
- 不執行，不收費（真正的 pay-as-you-go）

---

## Lambda 的限制與注意事項

雖然 Lambda 很香，但也有一些天然限制要注意：

<table id="bkmrk-%E9%99%90%E5%88%B6%E9%A0%85%E7%9B%AE-%E8%AA%AA%E6%98%8E-%E6%9C%80%E5%A4%A7%E5%9F%B7%E8%A1%8C%E6%99%82%E9%96%93-15-%E5%88%86%E9%90%98"><thead><tr><th>限制項目</th><th>說明</th></tr></thead><tbody><tr><td>最大執行時間</td><td>15 分鐘</td></tr><tr><td>單次部署包大小上限</td><td>250MB（含 layer）</td></tr><tr><td>同步觸發最大負載</td><td>預設 1000 concurrency（可申請提升）</td></tr><tr><td>須設計無狀態</td><td>每次執行是全新環境，不保留記憶體資料</td></tr></tbody></table>

---

## 小結

今天介紹了 AWS Lambda 的核心概念：

- 完全免維護伺服器
- 按次計費、真正用多少付多少
- 適合 API、小型運算任務、背景作業
- 超級擴展性，適合不確定流量的應用

Lambda 完全顛覆了傳統開發部署方式，讓開發者能更專注在**功能本身**，而不是維護伺服器！

---

好！這裡直接幫你產出  
**AWS Lambda 實作教學（Hello World + API Gateway 快速串接）**  
讓讀者可以跟著馬上做出第一個 Lambda Function，並且用瀏覽器呼叫成功！

---

# AWS Lambda 實作教學（Hello World + API Gateway 快速串接）

前一篇我們認識了什麼是 AWS Lambda。  
這一篇直接來實戰，帶你：

- 建立一個 Hello World Lambda
- 用 API Gateway 快速串接
- 用瀏覽器直接呼叫！

超簡單，只需要 10 分鐘 🚀

---

## 1. 建立第一個 Lambda Function

登入 AWS Console  
打開 [AWS Lambda Console](https://console.aws.amazon.com/lambda/)

1. 點選「Create function」
2. 選擇「Author from scratch」
3. 填寫基本資訊：

<table id="bkmrk-%E9%A0%85%E7%9B%AE-%E8%A8%AD%E5%AE%9A-function-name-"><thead><tr><th>項目</th><th>設定</th></tr></thead><tbody><tr><td>Function name</td><td>`hello-world-lambda`</td></tr><tr><td>Runtime</td><td>Node.js 18.x（或 Python 3.11 也可）</td></tr><tr><td>Architecture</td><td>x86\_64（預設即可）</td></tr><tr><td>Permissions</td><td>建議選「Create a new role with basic Lambda permissions」</td></tr></tbody></table>

4. 點選「Create Function」

幾秒鐘後，一個新的 Lambda 就建立好了！

---

## 2. 編輯 Lambda 程式碼

在 Lambda Console 的編輯器中，把預設程式碼改成這樣：

### Node.js 版

```javascript
exports.handler = async (event) => {
    return {
        statusCode: 200,
        body: JSON.stringify('Hello from AWS Lambda!'),
    };
};

```

### Python 版

```python
def lambda_handler(event, context):
    return {
        'statusCode': 200,
        'body': 'Hello from AWS Lambda!'
    }

```

編輯完後，記得按下「Deploy」！

這樣 Lambda 程式就設定完成了 🎯

---

## 3. 建立 API Gateway 串接 Lambda

接下來，我們讓這個 Lambda 能夠用 HTTP API 被呼叫。

1. 在 Lambda Function 頁面上方，點選「Add trigger」
2. 選擇「API Gateway」
3. 設定：

<table id="bkmrk-%E9%A0%85%E7%9B%AE-%E8%A8%AD%E5%AE%9A-api-type-http-"><thead><tr><th>項目</th><th>設定</th></tr></thead><tbody><tr><td>API type</td><td>HTTP API（比較簡單快速）</td></tr><tr><td>Security</td><td>Open（不設驗證，之後可以補強）</td></tr><tr><td>API Name</td><td>自動生成或自己命名也可以</td></tr></tbody></table>

4. 點選「Add」

AWS 會自動幫你建立一個 HTTP API，並串好 Lambda！

完成後，Lambda 頁面會顯示一個 API Endpoint，例如：

```
https://xxxxx12345.execute-api.ap-northeast-1.amazonaws.com/

```

---

## 4. 測試 API 呼叫

打開你的瀏覽器，直接輸入剛剛那個 URL！

你應該會看到畫面顯示：

```
"Hello from AWS Lambda!"

```

恭喜 🎉  
你的第一個 Lambda + API Gateway 已經成功部署！

---

## 5. 小結

這次的流程總結：

<table id="bkmrk-%E6%AD%A5%E9%A9%9F-%E8%AA%AA%E6%98%8E-%E5%BB%BA%E7%AB%8B-lambda-%E6%92%B0%E5%AF%AB-h"><thead><tr><th>步驟</th><th>說明</th></tr></thead><tbody><tr><td>建立 Lambda</td><td>撰寫 Hello World 程式碼</td></tr><tr><td>建立 API Gateway Trigger</td><td>快速建立 HTTP API 串接 Lambda</td></tr><tr><td>測試呼叫</td><td>瀏覽器直接打 API URL 測試結果</td></tr></tbody></table>

透過 Lambda + API Gateway，可以超快速上線一個基本 API，  
而且完全不用管 EC2、Scaling、Load Balancer 等基礎設施！

---

太好了！這裡接著給你完整的  
**AWS Lambda 進階教學：環境變數、IAM 權限設定、連接 RDS 資料庫**，  
讓你的 Lambda 應用從 Hello World 升級到真正可以跑正式服務！

---

# AWS Lambda 進階教學

（環境變數設定＋IAM 權限管理＋連接 RDS 資料庫）

---

## 一、設定 Lambda 環境變數（Environment Variables）

### ✅ 為什麼需要環境變數？

- 存放敏感資訊（資料庫帳號密碼、API Key）
- 存放不同環境（dev / prod）設定
- 程式更乾淨，不要把設定寫死在程式碼裡

### ✅ 如何設定環境變數

1. 打開 Lambda Function 頁面
2. 選擇「Configuration」→「Environment variables」
3. 點「Edit」→「Add environment variable」
4. 加入變數，例如：

<table id="bkmrk-key-value-db_host-yo"><thead><tr><th>Key</th><th>Value</th></tr></thead><tbody><tr><td>`DB_HOST`</td><td>`your-db.xxxx.rds.amazonaws.com`</td></tr><tr><td>`DB_USER`</td><td>`admin`</td></tr><tr><td>`DB_PASSWORD`</td><td>`your-password`</td></tr><tr><td>`DB_NAME`</td><td>`your-database-name`</td></tr></tbody></table>

5. 儲存即可，Lambda 會自動讀取這些變數

---

### ✅ 程式裡讀取環境變數（Python範例）

```python
import os

db_host = os.environ['DB_HOST']
db_user = os.environ['DB_USER']
db_password = os.environ['DB_PASSWORD']
db_name = os.environ['DB_NAME']

```

讀取環境變數超簡單，推薦大家一定要用這種方式管理設定！

---

## 二、設定 Lambda 的 IAM Role（權限）

### ✅ 為什麼需要設定 IAM Role？

Lambda 本身如果要呼叫其他 AWS 資源（像 S3、DynamoDB、RDS），需要有「授權」。

這個授權是透過「IAM Role」給的。

> 📌 Lambda 本身就會綁定一個 IAM Role，進行細部設定即可。

---

### ✅ 最小權限原則（Best Practice）

- 只開放 Lambda 需要用到的權限
- 不要直接給 Lambda admin 權限（避免安全漏洞）

---

### ✅ 範例：讓 Lambda 存取 RDS（或 VPC）

如果 Lambda 需要連到 RDS，需要設定：

- 讓 Lambda 進入正確的 VPC、Subnet、Security Group
- IAM Role 不需要特別開 RDS 權限（除非要操作 RDS API，例如開啟 RDS Snapshot）

所以通常 IAM Role 保持「基本執行」權限就好，例如：

- `AWSLambdaBasicExecutionRole`（CloudWatch log 權限）
- `AWSLambdaVPCAccessExecutionRole`（存取 VPC 權限）

可以在 Lambda → Configuration → Permissions → Execution Role 編輯。

---

## 三、Lambda 連接 RDS 資料庫（Python 範例）

假設我們已經設定好 RDS MySQL 或 PostgreSQL 資料庫，並且把連線資訊設在環境變數。

### ✅ 安裝必備套件

AWS Lambda 本身環境很乾淨，需要把 Python 連線資料庫的套件（例如 `pymysql`、`psycopg2`）打包進去。

**開發環境步驟：**

```bash
mkdir lambda_rds
cd lambda_rds
python3 -m venv venv
source venv/bin/activate
pip install pymysql
deactivate

```

把 `venv/lib/python3.11/site-packages` 的 pymysql 複製到專案目錄。

最後 ZIP 包含：

- `lambda_function.py`（你的程式）
- `pymysql/`（套件目錄）

---

### ✅ Lambda Python 連線 RDS (MySQL) 範例

```python
import pymysql
import os

def lambda_handler(event, context):
    # 讀取環境變數
    db_host = os.environ['DB_HOST']
    db_user = os.environ['DB_USER']
    db_password = os.environ['DB_PASSWORD']
    db_name = os.environ['DB_NAME']
    
    # 建立連線
    connection = pymysql.connect(
        host=db_host,
        user=db_user,
        password=db_password,
        database=db_name,
        connect_timeout=5
    )
    
    try:
        with connection.cursor() as cursor:
            sql = "SELECT NOW();"
            cursor.execute(sql)
            result = cursor.fetchone()
            return {
                'statusCode': 200,
                'body': f'Database Time: {result[0]}'
            }
    finally:
        connection.close()

```

這樣就能讓 Lambda 成功連接雲端資料庫，完成資料操作！

---

## 四、常見注意事項

<table id="bkmrk-%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A0%85-%E8%AA%AA%E6%98%8E-lambda-%2B-rds"><thead><tr><th>注意事項</th><th>說明</th></tr></thead><tbody><tr><td>Lambda + RDS Latency</td><td>Lambda 通常要設在與 RDS 同一個 VPC / AZ</td></tr><tr><td>連線池設計</td><td>建議使用 RDS Proxy（可以省連線數）</td></tr><tr><td>超時設置</td><td>Lambda 最大執行時間 15 分鐘，連資料庫要小心逾時</td></tr><tr><td>打包套件</td><td>自訂套件一定要打包進 ZIP 中</td></tr></tbody></table>

---

# 小結

這篇進階教學重點整理：

- Lambda 用環境變數管理設定
- Lambda 設定 IAM Role，最小權限原則
- Lambda 可以直接連 RDS，但要注意 VPC 設定
- 打包套件進 ZIP 是必須的！

掌握這些，  
你的 Lambda 就可以從 Hello World 升級成**正式後端服務**了！

---

# AWS Serverless 全應用實戰

（API Gateway + Lambda + DynamoDB + RDS Proxy）

---

## 🔧 架構圖

<div id="bkmrk-css-%E8%A4%87%E8%A3%BD%E7%B7%A8%E8%BC%AF-%5Bclient%5D-%E2%86%93-"><div>css</div><div><div><div><span class="" data-state="closed"><button aria-label="複製" class="flex gap-1 items-center select-none px-4 py-1"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>複製</button></span><span class="" data-state="closed"><button class="flex items-center gap-1 px-4 py-1 select-none"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>編輯</button></span></div></div></div><div>`<span class="hljs-selector-attr">[Client]</span>   ↓<span class="hljs-selector-attr">[API Gateway]</span>   ↓<span class="hljs-selector-attr">[AWS Lambda]</span>   ↓               ↘<span class="hljs-selector-attr">[DynamoDB]</span>      <span class="hljs-selector-attr">[RDS Proxy]</span> → <span class="hljs-selector-attr">[MySQL / PostgreSQL]</span>`</div></div>---

## 一、建立 DynamoDB Table

1. 打開 [DynamoDB Console](https://console.aws.amazon.com/dynamodb)
2. 點選「Create Table」
3. 設定：

<div id="bkmrk-%E9%A0%85%E7%9B%AE-%E5%80%BC-table-name-todo"><button class="hover:bg-token-main-surface-secondary text-token-text-secondary pointer-events-auto rounded-lg px-1 py-1 opacity-0 transition-opacity duration-200 group-focus-within:opacity-100 group-hover:opacity-100"><svg class="icon-md-heavy" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg></button><div><table class="min-w-full" data-end="752" data-start="610"><thead data-end="643" data-start="610"><tr data-end="643" data-start="610"><th data-end="624" data-start="610">項目</th><th data-end="643" data-start="624">值</th></tr></thead><tbody data-end="752" data-start="681"><tr data-end="717" data-start="681"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="697" data-start="681">Table name</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="717" data-start="697">`Todos`</td></tr><tr data-end="752" data-start="718"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="734" data-start="718">Partition key</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="752" data-start="734">`id`（String）</td></tr></tbody></table>

</div></div>4. 其他預設即可，點「Create table」

---

## 二、撰寫 Lambda Function

### ✅ Lambda 功能：新增一筆 Todo 資料

程式語言以 Python 為例，並假設請求是 `POST`，Body 包含 `title` 欄位。

<div id="bkmrk-python-%E8%A4%87%E8%A3%BD%E7%B7%A8%E8%BC%AF-import-j"><div>python</div><div><div><div><span class="" data-state="closed"><button aria-label="複製" class="flex gap-1 items-center select-none px-4 py-1"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>複製</button></span><span class="" data-state="closed"><button class="flex items-center gap-1 px-4 py-1 select-none"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>編輯</button></span></div></div></div><div>`<span class="hljs-keyword">import</span> json<span class="hljs-keyword">import</span> uuid<span class="hljs-keyword">import</span> boto3dynamodb = boto3.resource(<span class="hljs-string">'dynamodb'</span>)table = dynamodb.Table(<span class="hljs-string">'Todos'</span>)<span class="hljs-keyword">def</span> <span class="hljs-title function_">lambda_handler</span>(<span class="hljs-params">event, context</span>):    body = json.loads(event[<span class="hljs-string">'body'</span>])    todo_id = <span class="hljs-built_in">str</span>(uuid.uuid4())    title = body.get(<span class="hljs-string">'title'</span>)    table.put_item(        Item={            <span class="hljs-string">'id'</span>: todo_id,            <span class="hljs-string">'title'</span>: title        }    )    <span class="hljs-keyword">return</span> {        <span class="hljs-string">'statusCode'</span>: <span class="hljs-number">200</span>,        <span class="hljs-string">'body'</span>: json.dumps({<span class="hljs-string">'id'</span>: todo_id, <span class="hljs-string">'title'</span>: title})    }`</div></div>✅ Lambda 要有以下 IAM 權限：

<div id="bkmrk-json-%E8%A4%87%E8%A3%BD%E7%B7%A8%E8%BC%AF-%7B-%22effect%22"><div>json</div><div><div><div><span class="" data-state="closed"><button aria-label="複製" class="flex gap-1 items-center select-none px-4 py-1"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>複製</button></span><span class="" data-state="closed"><button class="flex items-center gap-1 px-4 py-1 select-none"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>編輯</button></span></div></div></div><div>`<span class="hljs-punctuation">{</span>  <span class="hljs-attr">"Effect"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Allow"</span><span class="hljs-punctuation">,</span>  <span class="hljs-attr">"Action"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>    <span class="hljs-string">"dynamodb:PutItem"</span>  <span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>  <span class="hljs-attr">"Resource"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"arn:aws:dynamodb:*:*:table/Todos"</span><span class="hljs-punctuation">}</span>`</div></div>> 可以在 Lambda 執行角色（Execution Role）中加上這段 policy。

---

## 三、建立 API Gateway 串接 Lambda

1. 打開 [API Gateway Console](https://console.aws.amazon.com/apigateway)
2. 建立一個「HTTP API」
3. 新增路由：
    
    
    - 方法：`POST`
    - 路徑：`/todos`
    - 整合：選擇剛剛的 Lambda
4. Deploy 完後，你會得到一個 API URL

使用 curl 或 Postman 測試：

<div id="bkmrk-bash-%E8%A4%87%E8%A3%BD%E7%B7%A8%E8%BC%AF-curl--x-po"><div>bash</div><div><div><div><span class="" data-state="closed"><button aria-label="複製" class="flex gap-1 items-center select-none px-4 py-1"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>複製</button></span><span class="" data-state="closed"><button class="flex items-center gap-1 px-4 py-1 select-none"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>編輯</button></span></div></div></div><div>`curl -X POST https://xxx.execute-api.ap-northeast-1.amazonaws.com/todos \  -H <span class="hljs-string">"Content-Type: application/json"</span> \  -d <span class="hljs-string">'{"title":"Write Lambda guide"}'</span>`</div></div>---

## 四、加碼：改用 RDS + RDS Proxy 作為資料層

### ✅ 為什麼使用 RDS Proxy？

- 解決 Lambda 對 RDS 連線過多問題
- 支援連線池（Connection pooling）
- 提高效率，降低 DB crash 風險

---

### 步驟 1：建立 RDS Proxy

1. 前往 [RDS Console](https://console.aws.amazon.com/rds)
2. 選單點「Proxies」→「Create proxy」
3. 設定：

<div id="bkmrk-%E9%A0%85%E7%9B%AE-%E5%80%BC-proxy-name-todo"><button class="hover:bg-token-main-surface-secondary text-token-text-secondary pointer-events-auto rounded-lg px-1 py-1 opacity-0 transition-opacity duration-200 group-focus-within:opacity-100 group-hover:opacity-100"><svg class="icon-md-heavy" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg></button><div><table class="min-w-full" data-end="2582" data-start="2230"><thead data-end="2280" data-start="2230"><tr data-end="2280" data-start="2230"><th data-end="2247" data-start="2230">項目</th><th data-end="2280" data-start="2247">值</th></tr></thead><tbody data-end="2582" data-start="2335"><tr data-end="2388" data-start="2335"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2354" data-start="2335">Proxy name</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2388" data-start="2354">`todo-proxy`</td></tr><tr data-end="2436" data-start="2389"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2408" data-start="2389">Engine</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2436" data-start="2408">MySQL / PostgreSQL（需有資料庫）</td></tr><tr data-end="2484" data-start="2437"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2456" data-start="2437">Target RDS</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2484" data-start="2456">選擇現有的 RDS 實例</td></tr><tr data-end="2531" data-start="2485"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2504" data-start="2485">IAM Auth</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2531" data-start="2504">啟用（建議使用）</td></tr><tr data-end="2582" data-start="2532"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2551" data-start="2532">VPC</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2582" data-start="2551">與 Lambda 相同 VPC</td></tr></tbody></table>

</div></div>> 建好後會給你一個 Proxy endpoint：例如 `todo-proxy.proxy-cdefg.ap-northeast-1.rds.amazonaws.com`

---

### 步驟 2：Lambda 改連 Proxy（Python）

安裝並打包 `pymysql`：

<div id="bkmrk-bash-%E8%A4%87%E8%A3%BD%E7%B7%A8%E8%BC%AF-pip-instal"><div>bash</div><div><div><div><span class="" data-state="closed"><button aria-label="複製" class="flex gap-1 items-center select-none px-4 py-1"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>複製</button></span><span class="" data-state="closed"><button class="flex items-center gap-1 px-4 py-1 select-none"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>編輯</button></span></div></div></div><div>`pip install pymysql -t .zip -r lambda-rds.zip .`</div></div>Lambda 程式碼範例：

<div id="bkmrk-python-%E8%A4%87%E8%A3%BD%E7%B7%A8%E8%BC%AF-import-p"><div>python</div><div><div><div><span class="" data-state="closed"><button aria-label="複製" class="flex gap-1 items-center select-none px-4 py-1"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>複製</button></span><span class="" data-state="closed"><button class="flex items-center gap-1 px-4 py-1 select-none"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>編輯</button></span></div></div></div><div>`<span class="hljs-keyword">import</span> pymysql<span class="hljs-keyword">import</span> os<span class="hljs-keyword">import</span> json<span class="hljs-keyword">import</span> uuid<span class="hljs-keyword">def</span> <span class="hljs-title function_">lambda_handler</span>(<span class="hljs-params">event, context</span>):    conn = pymysql.connect(        host=os.environ[<span class="hljs-string">'DB_PROXY_HOST'</span>],  <span class="hljs-comment"># Proxy endpoint</span>        user=os.environ[<span class="hljs-string">'DB_USER'</span>],        password=os.environ[<span class="hljs-string">'DB_PASSWORD'</span>],        database=os.environ[<span class="hljs-string">'DB_NAME'</span>],        connect_timeout=<span class="hljs-number">5</span>    )        <span class="hljs-keyword">try</span>:        body = json.loads(event[<span class="hljs-string">'body'</span>])        todo_id = <span class="hljs-built_in">str</span>(uuid.uuid4())        title = body[<span class="hljs-string">'title'</span>]                <span class="hljs-keyword">with</span> conn.cursor() <span class="hljs-keyword">as</span> cursor:            cursor.execute(<span class="hljs-string">"INSERT INTO todos (id, title) VALUES (%s, %s)"</span>, (todo_id, title))            conn.commit()        <span class="hljs-keyword">return</span> {            <span class="hljs-string">'statusCode'</span>: <span class="hljs-number">200</span>,            <span class="hljs-string">'body'</span>: json.dumps({<span class="hljs-string">'id'</span>: todo_id, <span class="hljs-string">'title'</span>: title})        }    <span class="hljs-keyword">finally</span>:        conn.close()`</div></div>---

### ✅ 注意：

- Lambda 要設定進入 **同 VPC + Subnet + SG**
- 建議設定 `AWSLambdaVPCAccessExecutionRole`
- Lambda 的 IAM Role 不需要存取 RDS Proxy，只需要 DB 連線資訊與網路存取權

---

## 五、小結

這篇教你從 0 建立一個完整的無伺服器應用：

<div id="bkmrk-%E5%85%83%E4%BB%B6-%E7%94%A8%E9%80%94-api-gateway-%E6%8F%90%E4%BE%9B"><button class="hover:bg-token-main-surface-secondary text-token-text-secondary pointer-events-auto rounded-lg px-1 py-1 opacity-0 transition-opacity duration-200 group-focus-within:opacity-100 group-hover:opacity-100"><svg class="icon-md-heavy" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg></button><div><table class="min-w-full" data-end="4064" data-start="3781"><thead data-end="3828" data-start="3781"><tr data-end="3828" data-start="3781"><th data-end="3798" data-start="3781">元件</th><th data-end="3828" data-start="3798">用途</th></tr></thead><tbody data-end="4064" data-start="3881"><tr data-end="3927" data-start="3881"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="3900" data-start="3881">API Gateway</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="3927" data-start="3900">提供外部 API 接口</td></tr><tr data-end="3974" data-start="3928"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="3947" data-start="3928">Lambda</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="3974" data-start="3947">執行商業邏輯 Function</td></tr><tr data-end="4022" data-start="3975"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="3994" data-start="3975">DynamoDB / RDS</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="4022" data-start="3994">儲存資料</td></tr><tr data-end="4064" data-start="4023"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="4042" data-start="4023">RDS Proxy</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="4064" data-start="4042">提升連線穩定性與效率</td></tr></tbody></table>

</div></div>這套架構非常適合：

- 小型 SaaS API
- 行動 App 後端
- MVP 初期應用快速開發
- 大型應用中的 Serverless 子系統（例如上傳任務、登入、Webhook 處理等）

---

太好了！這裡是你完整的加強版：  
**AWS Serverless Todo API 進階整合：Cognito 驗證、錯誤處理、Serverless Framework 快速部署**

這篇會在前面 Lambda + API Gateway + DynamoDB/RDS 架構上，補上以下三個常用進階功能：

1. ✅ **API Gateway JWT 驗證（Cognito）**
2. ✅ **Lambda 錯誤處理與 Logging 最佳實踐**
3. ✅ **使用 Serverless Framework 快速部署整套應用**

---

# Serverless Todo API 完整實戰進階篇

---

## 一、加入 Cognito 驗證保護 API

### ✅ 為什麼需要？

不管是 Web 前端還是 App，只要對外開 API，**基本保護機制不可少**。

AWS 的 Cognito 可以幫你處理：

- 使用者註冊 / 登入
- JWT Token 簽發與驗證
- 無需自建帳號系統！

---

### ✅ 建立 Cognito User Pool

1. 開啟 [Cognito Console](https://console.aws.amazon.com/cognito)
2. 建立一個 **User Pool**
    
    
    - Pool name: `todo-api-users`
    - 啟用 Email 登入即可
    - 完成後記住 Pool ID 和 Region
3. 建立 App Client
    
    
    - 禁用 client secret（Lambda 驗證用不到）
    - 儲存產生的 `App client ID`

---

### ✅ API Gateway 整合 JWT 驗證

1. 回到 API Gateway HTTP API Console
2. 選「Authorization」→「Create authorizer」
3. 建立 Cognito JWT authorizer：

<table id="bkmrk-%E9%A0%85%E7%9B%AE-%E8%A8%AD%E5%AE%9A-type-jwt-ident"><thead><tr><th>項目</th><th>設定</th></tr></thead><tbody><tr><td>Type</td><td>JWT</td></tr><tr><td>Identity source</td><td>`Authorization` header</td></tr><tr><td>Issuer URL</td><td>`https://cognito-idp.<region>.amazonaws.com/<pool-id>`</td></tr><tr><td>Audience</td><td>你剛剛的 App Client ID</td></tr></tbody></table>

4. 把 `/todos` 路徑設定為需要驗證（選上剛剛的 authorizer）

---

### ✅ 呼叫 API 時加上 JWT token

前端從 Cognito 登入取得 Token 後，呼叫 API 時帶上：

```http
Authorization: Bearer <token>

```

這樣 API Gateway 就會自動驗證使用者身份，Lambda 裡也可以透過 `event['requestContext']` 取得使用者資訊！

---

## 二、Lambda Logging 與 Error Handling 最佳實踐

### ✅ 記錄 Log 到 CloudWatch

在 Lambda 中使用標準輸出即可：

```python
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

logger.info("New todo created")

```

CloudWatch Logs 會自動收集這些 log。

---

### ✅ 最佳錯誤處理模式

```python
import json
import traceback

def lambda_handler(event, context):
    try:
        # 主邏輯
        ...
        return {
            'statusCode': 200,
            'body': json.dumps({'message': 'Success'})
        }
    except Exception as e:
        logger.error("Error: %s", traceback.format_exc())
        return {
            'statusCode': 500,
            'body': json.dumps({'error': 'Internal Server Error'})
        }

```

這樣可避免直接顯示錯誤堆疊給使用者，又方便自己 debug。

---

## 三、使用 Serverless Framework 快速部署整套應用

### ✅ 為什麼用 Serverless Framework？

- 自動建 Lambda、API Gateway、DynamoDB、IAM、環境變數
- 一鍵部署、一鍵刪除
- 適合團隊開發與 CI/CD

---

### ✅ 安裝 CLI 工具

```bash
npm install -g serverless

```

---

### ✅ 初始化專案

```bash
serverless create --template aws-python --path todo-api
cd todo-api

```

---

### ✅ 編輯 `serverless.yml`

以下是 Todo API + DynamoDB + JWT 驗證的簡化範例：

```yaml
service: todo-api

provider:
  name: aws
  runtime: python3.11
  region: ap-northeast-1
  environment:
    DB_TABLE: Todos
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:PutItem
      Resource: arn:aws:dynamodb:*:*:table/Todos

functions:
  createTodo:
    handler: handler.create
    events:
      - http:
          path: todos
          method: post
          authorizer:
            type: COGNITO_USER_POOLS
            userPoolArn: arn:aws:cognito-idp:ap-northeast-1:xxxx:userpool/xxxx

resources:
  Resources:
    TodosTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: Todos
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        BillingMode: PAY_PER_REQUEST

```

---

### ✅ Lambda 程式 `handler.py`

```python
import json
import uuid
import boto3
import os

table = boto3.resource('dynamodb').Table(os.environ['DB_TABLE'])

def create(event, context):
    data = json.loads(event['body'])
    todo_id = str(uuid.uuid4())

    table.put_item(Item={'id': todo_id, 'title': data['title']})

    return {
        'statusCode': 200,
        'body': json.dumps({'id': todo_id, 'title': data['title']})
    }

```

---

### ✅ 部署指令

```bash
sls deploy

```

幾十秒後，你就會拿到公開的 API URL！🎉

---

### ✅ 刪除整個堆疊（清資源）

```bash
sls remove

```

超級適合測試環境清除資源時使用，防止殘留帳單！

---

## ✅ 加碼：Lambda 搭配 RDS Proxy 用 Serverless Framework 管理

你可以在 `serverless.yml` 裡加上 VPC 設定：

```yaml
provider:
  ...
  vpc:
    securityGroupIds:
      - sg-xxxxxxxx
    subnetIds:
      - subnet-aaaa
      - subnet-bbbb

```

這樣部署後的 Lambda 就能存取你已經建立好的 RDS Proxy！

---

## 🔚 小結

這次進階整合包含：

✅ Cognito 驗證 ➜ 提供安全 API  
✅ Lambda Logging + Error Handling ➜ 更穩定、更易除錯  
✅ Serverless Framework ➜ 快速一鍵部署 / 一鍵刪除  
✅ 支援 DynamoDB / RDS Proxy ➜ 彈性資料層選擇

這套完整架構，適合你用來開發：

- SaaS MVP
- 微服務架構中的 User Service
- 內部工具（支援 Cognito 驗證）
- 全無伺服器 RESTful API

---

下一篇，我們將繼續探討  
**Day 9：AWS 容器服務 - EKS（Elastic Kubernetes Service）**，  
看看當需要更複雜的大型應用，想用 Kubernetes 架構時，AWS 是怎麼支援的！

Day 9 再見 👋

# 自動化測試

# APP自動化測試環境建置

### 建立應用程式 UI 自動化測試 - 以 Robot Framework 為例

[https://ithelp.ithome.com.tw/users/20168859/ironman/7650](https://ithelp.ithome.com.tw/users/20168859/ironman/7650)

java

[Java Downloads | Oracle 台灣](https://www.oracle.com/tw/java/technologies/downloads/#jdk24-windows)

jdk-24\_windows-x64\_bin

[![image-1751637163184.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/scaled-1680-/sC3qj9o1IF7VUtG3-image-1751637163184.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/sC3qj9o1IF7VUtG3-image-1751637163184.png)

[![image-1751637195203.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/scaled-1680-/Qzx26p4r3tG65iWk-image-1751637195203.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/Qzx26p4r3tG65iWk-image-1751637195203.png)

install appium  
[https://ithelp.ithome.com.tw/articles/10360843](https://ithelp.ithome.com.tw/articles/10360843)

  
Robot Framework 安裝及初體驗  
  
install python  
[https://www.python.org/downloads/](https://www.python.org/downloads/)  
install nvm  
[https://github.com/coreybutler/nvm-windows](https://github.com/coreybutler/nvm-windows)

nvm install lts  
nvm use 22.16.0  
npm install -g appium

[![image-1751630234131.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/scaled-1680-/gbEwaM7upRkr1RJE-image-1751630234131.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/gbEwaM7upRkr1RJE-image-1751630234131.png)

[![image-1750756211301.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-06/scaled-1680-/9Mq8s9M0MIKaXZDy-image-1750756211301.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-06/9Mq8s9M0MIKaXZDy-image-1750756211301.png)

[![image-1750756233619.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-06/scaled-1680-/hzHWmUo1eUXZxv1V-image-1750756233619.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-06/hzHWmUo1eUXZxv1V-image-1750756233619.png)

install appium-inspector  
[https://github.com/appium/appium-inspector](https://github.com/appium/appium-inspector)

[![image-1750757422111.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-06/scaled-1680-/wpF2SSsT8R3qjFIW-image-1750757422111.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-06/wpF2SSsT8R3qjFIW-image-1750757422111.png)

```JSON
{
  "platformName": "Android",
  "appium:automationName": "uiautomator2",
  "appium:deviceName": "Android",
  "appium:appPackage": "com.android.settings",
  "appium:appActivity": ".Settings"
}
```

```shell
# 安裝其他套件

pip install robotframework
pip install robotframework-appiumlibrary
```

要讓 Appium 透過 USB 控制實體手機（例如 Android）來做測試，你需要讓你的 **notebook（筆電）與手機透過 USB 連線**，並配合幾個工具來完成連接。這裡用 **Android + Windows notebook** 為範例流程說明。

---

## ✅ 一、前置條件檢查

### 📱 手機端設定：

1. **開啟「開發者選項」**
    
    
    - 到手機設定 → 關於手機 → 點「版本號」七次 → 解鎖開發者模式
2. **打開 USB 偵錯**
    
    
    - 設定 → 系統 → 開發者選項 → 開啟「USB 偵錯」
3. **用 USB 傳輸線接上筆電**

---

## ✅ 二、筆電端（Windows）準備

### 1️⃣ 安裝 ADB（Android Debug Bridge）

#### 最快方法：安裝 Android Platform Tools（免裝 Android Studio）：

👉 下載網址：[https://developer.android.com/studio/releases/platform-tools](https://developer.android.com/studio/releases/platform-tools)

解壓縮後，把 `platform-tools` 資料夾加到環境變數 `PATH`。

### Windows

#### 如果安裝 Andrioid Studio 

```shell
#位置在
C:\Users\<你的帳號>\AppData\Local\Android\Sdk\platform-tools\adb.exe

```

#### 設定系統環境變數

1. 按下 Windows 鍵 → 輸入「環境變數」→ 選擇「編輯系統環境變數」
2. 點「環境變數（Environment Variables）」
3. 在 **使用者變數** 或 **系統變數** 中新增：
    
    <div><div><table class="w-fit min-w-(--thread-content-width)" data-end="802" data-start="531"><thead data-end="592" data-start="531"><tr data-end="592" data-start="531"><th data-col-size="sm" data-end="546" data-start="531">變數名稱</th><th data-col-size="sm" data-end="592" data-start="546">變數值</th></tr></thead><tbody data-end="802" data-start="667"><tr data-end="734" data-start="667"><td data-col-size="sm" data-end="686" data-start="667">`ANDROID_HOME`</td><td data-col-size="sm" data-end="734" data-start="686">`C:\Users\MOMO\AppData\Local\Android\Sdk`</td></tr><tr data-end="802" data-start="738"><td data-col-size="sm" data-end="763" data-start="738">`ANDROID_SDK_ROOT`（可選）</td><td data-col-size="sm" data-end="802" data-start="763">同上</td></tr></tbody></table>
    
    <div><div><button aria-label="複製表格" class="hover:bg-token-bg-tertiary text-token-text-secondary my-1 rounded-sm p-1 transition-opacity group-[:not(:hover):not(:focus-within)]:pointer-events-none group-[:not(:hover):not(:focus-within)]:opacity-0"><svg class="icon" fill="currentColor" height="20" viewbox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"></svg></button></div></div></div></div>
4. 編輯 `Path` 變數 → 加入以下路徑

```
%ANDROID_HOME%\platform-tools
%ANDROID_HOME%\emulator
```

完成後關閉並重新啟動 cmd / PowerShell / VSCode terminal

### Mac

```shell
brew install --cask android-platform-tools
```

驗證：

```cmd
adb devices

```

第一次會提示你在手機上授權，允許 USB 偵錯。

---

## ✅ 三、安裝 Appium 驅動（UIAutomator2）

```bash
appium driver install uiautomator2

```

確認：

```bash
appium driver list

```

---

## ✅ 四、啟動 Appium Server

```bash
appium

```

預設會在 `http://localhost:4723` 開啟伺服器。

---

## ✅ 五、撰寫測試程式連接手機

### 📝 以 Python 為例（也可用 Java、JS 等）

```python
from appium import webdriver

desired_caps = {
    "platformName": "Android",
    "deviceName": "Android Device",       # 或填 adb devices 顯示的 ID
    "appPackage": "com.android.settings", # 測試用，可改成你自己的 app
    "appActivity": ".Settings",
    "automationName": "UiAutomator2"
}

driver = webdriver.Remote("http://localhost:4723/wd/hub", desired_caps)

driver.quit()

```

---

## ✅ 六、常見問題排解

<table id="bkmrk-%E5%95%8F%E9%A1%8C-%E8%A7%A3%E6%B3%95-adb-devices-%E7%9C%8B%E4%B8%8D"><thead><tr><th>問題</th><th>解法</th></tr></thead><tbody><tr><td>`adb devices` 看不到手機</td><td>檢查線材、USB 傳輸模式是否為「資料傳輸」，確認有開 USB 偵錯</td></tr><tr><td>Appium 找不到設備</td><td>確認驅動安裝好、手機有授權、Appium Server 已啟動</td></tr><tr><td>Appium 錯誤：no device found</td><td>驗證 `adb devices` 有成功列出設備</td></tr><tr><td>Appium Inspector 無法開啟 session</td><td>確認 desired capabilities 正確，並與 appium port 對上</td></tr></tbody></table>

---

如果你想搭配 Robot Framework 來寫測試，也可以再幫你整合 robotframework-appiumlibrary 的使用方式。

是否也需要說明 iOS 連線流程（macOS + iPhone 測試）？

# APP自動化測試安裝(IOS)

[![image-1751525865876.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/scaled-1680-/HHUWki8YTqfqFbYg-image-1751525865876.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/HHUWki8YTqfqFbYg-image-1751525865876.png)

[![image-1751526020594.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/scaled-1680-/rdu16aqFYZkgvloF-image-1751526020594.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/rdu16aqFYZkgvloF-image-1751526020594.png)

```
$xcode-select -p
/Applications/Xcode.app/Contents/Developer
$xcode-select -print-path
/Applications/Xcode.app/Contents/Developer
```

[![image-1751526490734.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/scaled-1680-/dcaWjutMA4n3dUze-image-1751526490734.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/dcaWjutMA4n3dUze-image-1751526490734.png)

[![image-1751526646989.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/scaled-1680-/mN1HD2FlPbQ8vy5i-image-1751526646989.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/mN1HD2FlPbQ8vy5i-image-1751526646989.png)

```
$xcrun simctl list devices
== Devices ==
-- iOS 17.0 --
    iPhone SE (3rd generation) (C2263CE0-B8D0-4A45-A8B6-819EBFDC6B3C) (Shutdown) 
    iPhone 15 (342CFDF8-F135-4C7E-B721-809DBAE2FED6) (Shutdown) 
    iPhone 15 Plus (A1502CF4-2655-41EF-ACCA-344DE1880989) (Shutdown) 
    iPhone 15 Pro (212B06CE-ABA0-4ED1-826C-E34313D29FD8) (Shutdown) 
    iPhone 15 Pro Max (C32A003C-E330-4653-98BD-67D69034B784) (Shutdown) 
    iPad Air (5th generation) (8B350383-3091-45C8-98A9-9EF9FAC09869) (Shutdown) 
    iPad (10th generation) (23A6B6DD-A65F-4EA4-8B20-D2504025FABB) (Shutdown) 
    iPad mini (6th generation) (2549DC38-10CE-4117-919C-2EA9F0105EFD) (Shutdown) 
    iPad Pro (11-inch) (4th generation) (1994EEF6-5F7D-4A93-A8E8-548988FF7F82) (Shutdown) 
    iPad Pro (12.9-inch) (6th generation) (96C9D1A6-AD3C-48DF-8A27-310800A51FF4) (Shutdown) 
-- iOS 18.1 --
    iPhone SE (3rd generation) (B50D832D-774F-4B7D-9D14-82395408AEA0) (Shutdown) 
    iPhone 16 Pro (1E598AB9-170B-429D-A45E-818CEFAC9B26) (Shutdown) 
    iPhone 16 Pro Max (E6888AFA-4A3E-4E18-BDB6-C270815306FE) (Shutdown) 
    iPhone 16 (AB625102-C6F4-425B-A280-D8C0E472A9B1) (Shutdown) 
    iPhone 16 Plus (AD5044C4-3EE2-4D41-897B-0CDC1AA82800) (Shutdown) 
    iPad (10th generation) (ACED15AA-AB6E-4B6B-A453-53A139698288) (Shutdown) 
    iPad Air 11-inch (M2) (3F29CBE6-098A-407E-B2F0-C03F10ACB3BC) (Shutdown) 
    iPad Air 13-inch (M2) (1E1CBA41-26E4-4825-B78F-9B1218B45CBD) (Shutdown) 
    iPad mini (A17 Pro) (EFFCCE75-3E77-4131-8BF3-1A2D23EDDC9A) (Shutdown) 
    iPad Pro 11-inch (M4) (85ADF4A5-46FD-4601-8B29-BD21892E3512) (Shutdown) 
    iPad Pro 13-inch (M4) (43E67B57-74B9-4060-8C20-CB892863F586) (Shutdown) 
```

[![image-1751823189170.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/scaled-1680-/ECDeMdNbP1ybn5bV-image-1751823189170.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/ECDeMdNbP1ybn5bV-image-1751823189170.png)

[![image-1751823868524.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/scaled-1680-/iG2eWZqJlBkqs2L0-image-1751823868524.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-07/iG2eWZqJlBkqs2L0-image-1751823868524.png)

# 相關連結

### AppiumLibrary key word

##### [https://serhatbolsu.github.io/robotframework-appiumlibrary/AppiumLibrary.html#Introduction](https://bookstack.treemanou.com/AppiumLibrary)

### Appium 中文翻譯

##### [https://www.kancloud.cn/testerhome/appium\_docs\_cn/3209426](https://bookstack.treemanou.com/Appium)

# Appium、Python、Robot Framework 之間的關係

---

## ✅ Appium、Python、Robot Framework 之間的關係：

這三者可以整合在一起，是因為：

<table id="bkmrk-%E6%8A%80%E8%A1%93-%E8%A7%92%E8%89%B2-%E8%AA%AA%E6%98%8E-appium-%E6%B8%AC%E8%A9%A6%E5%BC%95%E6%93%8E"><thead><tr><th>技術</th><th>角色</th><th>說明</th></tr></thead><tbody><tr><td>**Appium**</td><td>測試引擎</td><td>提供 WebDriver API，控制手機（Android / iOS）的自動化操作</td></tr><tr><td>**Python**</td><td>實作擴充邏輯的程式語言</td><td>可以寫自己的測試邏輯、自訂關鍵字（如你現在做的）</td></tr><tr><td>**Robot Framework**</td><td>測試框架（高階語法）</td><td>用關鍵字驅動的方式來寫測試案例，支援 Python Library</td></tr></tbody></table>

---

## 🔄 它們如何互動？

```
[Robot Framework]
        |
        | 呼叫
        ↓
[自訂 Python 關鍵字]
        |
        | 呼叫
        ↓
[AppiumLibrary（內部就是 WebDriver）]
        |
        ↓
[Appium Server → 控制手機裝置]

```

---

### 📦 關鍵套件：

<table id="bkmrk-%E5%A5%97%E4%BB%B6%E5%90%8D%E7%A8%B1-%E7%94%A8%E9%80%94-appiumlibrar"><thead><tr><th>套件名稱</th><th>用途</th></tr></thead><tbody><tr><td>`AppiumLibrary`</td><td>Robot Framework 的套件，封裝了 Appium 操作</td></tr><tr><td>`robotframework`</td><td>測試框架本體</td></tr><tr><td>`appium-python-client`</td><td>Python 端 Appium 的驅動（AppiumLibrary 內部使用）</td></tr></tbody></table>

---

## ✅ 典型架構目錄

```
project/
├── tests/
│   └── test_login.robot
├── keywords/
│   └── custom_scroll.py   ← 這是你寫的 Python 關鍵字
├── resources/
│   └── variables.robot
├── requirements.txt

```

---

## ✅ Robot 測試如何叫到 Appium？

```robot
*** Settings ***
Library    AppiumLibrary
Library    keywords/custom_scroll.py

*** Test Cases ***
登入後登出
    Open Application    http://localhost:4723    platformName=iOS    ...
    Scroll To Text And Tap    登出

```

你寫的 `Scroll To Text And Tap` 是一個 Python 關鍵字，會去呼叫 Appium WebDriver 去執行滑動與點擊。

---

## ✅ 重點概念（幫你牢記）

- `Robot Framework` 是控制流程 + 組合測試邏輯。
- `AppiumLibrary` 是控制手機的橋樑（Python 包裝）。
- 你可以寫 Python function，然後 `@keyword` 暴露成 Robot 可以用的「關鍵字」。

---

如果你想我幫你整理一張「整合架構圖」或提供一份「快速入門模板」，我也可以幫你生成。需要嗎？

# AI相關

# gemini-cli

<span class="s1">來源: [https://github.com/google-gemini/gemini-cli](https://github.com/google-gemini/gemini-cli)  
參考: [https://www.darrelltw.com/google-gemini-cli](https://www.darrelltw.com/google-gemini-cli/)</span>

```p1
npm install -g @google/gemini-cli@latest
```

[![image-1750932966244.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-06/scaled-1680-/T0YcXze6kyTFGa2O-image-1750932966244.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-06/T0YcXze6kyTFGa2O-image-1750932966244.png)

[![image-1750932999983.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-06/scaled-1680-/8SKJPfMl6Mc7G0qV-image-1750932999983.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-06/8SKJPfMl6Mc7G0qV-image-1750932999983.png)

[![image-1750933039711.png](https://bookstack.treemanou.com/uploads/images/gallery/2025-06/scaled-1680-/ni8OWvw5naDJuH76-image-1750933039711.png)](https://bookstack.treemanou.com/uploads/images/gallery/2025-06/ni8OWvw5naDJuH76-image-1750933039711.png)

```shell
export GEMINI_API_KEY="AIzaSyDqmK6yd9rbj40LXXXXX" >> ~/.zshrc
source  ~/.zshrc
```

輸入 gemini 開始

# 【gemini】快速了解新專案

```
請對我當前目錄下的專案進行全面分析。我希望了解以下資訊：
1.  **專案概述：**
    * 這個專案的主要類型和目的為何？
    * 它旨在解決什麼問題？
2.  **核心技術棧：**
    * 請找出專案中使用的主要程式語言、框架和關鍵依賴庫。
    * 它是否使用了任何資料庫、容器化技術（如 Docker）或雲端服務？
3.  **資料夾結構分析：**
    * 請描述專案的頂層資料夾結構，並簡要說明每個主要資料夾（例如 `src/`、`public/`、`tests/`、`config/` 等）的用途。
    * 特別說明 `src/` 或 `app/` 目錄下主要的模組或元件是如何組織的。
4.  **程式碼行為與設計（高層次）：**
    * 請簡要說明專案的核心功能是如何實現的（例如，使用者認證、數據處理等）。
    * 是否有明顯的設計模式（例如 MVC）？
5.  **潛在問題或改進方向（初步）：**
    * 初步觀察下，是否有任何潛在的效能瓶頸、程式碼異味（code smell）或可維護性問題？

```

# 【gemini cli】【mcp】mongodb

<div id="bkmrk-"></div><div id="bkmrk-%7E%2F.gemini%2Fsettings.j">~/.gemini/settings.json</div><div id="bkmrk-%E5%9C%A8-mcpservers-%E4%B8%8B%EF%BC%8C%E6%96%B0%E5%A2%9E%E8%A8%AD%E5%AE%9A">在 mcpServers 下，新增設定</div>```JavaScript
{
  "theme": "Default",
    "selectedAuthType": "oauth-personal",
    "mcpServers": {
    	// => 加在這裡
  }
}
```

```JavaScript
// mongodb-env 名稱可以自訂
"mongodb-env": {
    "command": "npx",
      "args": [
        "-y",
	"mongodb-mcp-server",
        "--connectionString",
        "mongodb://{username}:{password}@{ip}:27017/?authSource=admin&readPreference=primary&directConnection=true&ssl=false",
        "--readOnly"
      ]
    }
```

完整設定如下

```shell
{
  "theme": "Default",
    "selectedAuthType": "oauth-personal",
    "mcpServers": {
    "mongodb-env": {
    "command": "npx",
      "args": [
        "-y",
	"mongodb-mcp-server",
        "--connectionString",
        "mongodb://{username}:{password}@{ip}:27017/?authSource=admin&readPreference=primary&directConnection=true&ssl=false",
        "--readOnly"
      ]
    }
  }
}
```

# 【gemini cli】shell function 呼叫 ai + mcp

新增一個ai專案資料夾

```
~/Documents/ai_workspace/gemini_func
```

<div id="bkmrk-%C2%A0%E6%96%B0%E5%A2%9E%E4%B8%80%E5%80%8B%E6%AA%94%E6%A1%88-mongo-otp-%28%E6%AD%A4"><div></div><div> 新增一個檔案 <span style="background-color: #fbeeb8;">mongo-otp</span> (此範例是透過mongo mcp 查詢otp)</div></div>```shell
  user_id_input=""
  model_to_use="gemini-2.5-flash"

  # --- 參數解析 ---
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --model)
        if [[ -n "$2" ]]; then
          model_to_use="$2"
          shift 2
        else
          echo " 錯誤：--model 參數需要模型名稱" >&2
          exit 1
        fi
        ;;
      -*)
        echo " 不支援的選項 '$1'" >&2
        exit 1
        ;;
      *)
        if [[ -n "$user_id_input" ]]; then
          echo " 錯誤：只能輸入一個 userId" >&2
          exit 1
        fi
        user_id_input="$1"
        shift
        ;;
    esac
  done

  if [[ -z "$user_id_input" ]]; then
    echo "請輸入 userId，例如：mongo-otp 11111" >&2
    exit 1
  fi

  echo "正在查詢 OTP，使用者 ID: $user_id_input"
  echo "使用模型: $model_to_use"
  echo "向 MCP 查詢 MongoDB..."

prompt=$(cat <<EOF
你是一位熟悉 MongoDB 的後端工程師，請根據下列需求準確地協助產出查詢結果，並同時附上正確的 MongoDB 查詢語法（db.collection.find(...)）以供我 debug。

1. 在 myDB.user collection 中，用 userId = "$user_id_input" 查出對應的 _id。
2. 接著在 myDB.captcha collection 中，查出 user_id = 上面查到的 _id 的資料。
3. 根據 modifyDate 欄位進行降序排序，只取出最新一筆。
4. 僅回傳該筆資料中的 captcha 欄位內容。
5. captcha.user_id 是字串型別，請不要使用 ObjectId(...)。

:pushpin: 最後請只回傳 captcha 的值，不包任何文字、說明、格式、MCP DEBUG 訊息等。
EOF
)

  otp=$(echo "$prompt" | gemini -m "$model_to_use" --no-sandbox | sed '/MCP STDERR/d;/\[DEBUG\]/d' | tail -n1)

  if [[ -n "$otp" ]]; then
    echo "OTP: $otp"
  else
    echo "找不到 OTP，可能使用者 ID 有誤或無資料。" >&2
    exit 1
  fi

```

 此範例需要設定 mongdob mcp: [https://bookstack.treemanou.com/books/treeman/page/gemini-climcpmongodb](https://bookstack.treemanou.com/books/treeman/page/gemini-climcpmongodb)

```shell
# 把ai專案下的檔案賦予執行權限
find ~/Documents/ai_workspace/gemini_func -type f -exec chmod +x {} \;
# 在 ~/.zshrc 把ai專案路徑加入環境變數，這樣之前的檔案就可以直接呼叫
export PATH="$PATH:$HOME/Documents/ai_workspace/gemini_func"

```

直接輸入 mongo-otp 就會執行shell，透過ai + mongo mcp 查詢

```
$mongo-otp 0011111
 正在查詢 OTP，使用者 ID: 0011111
 使用模型: gemini-2.5-flash
 向 MCP 查詢 MongoDB...
OTP: awsedr
```

# 【Gemini】Gemini vs Vertex AI

## **vertex ai 與 gemini. 是什麼關係**

---

簡單來說，**Vertex AI 是「平台」，而 Gemini 是「模型」**。

它們的關係可以類比為：**Vertex AI 是「廚房（包含所有工具和設備）」，而 Gemini 是「最頂級的食材或主廚」**。

以下是更詳細的關係拆解：

### 1. 定位上的不同

- **Gemini (模型)：** 是 Google 開發的最強大、最通用的**人工智慧模型家族**（包括 Gemini Ultra, Pro, Flash 等）。它具備多模態能力（能處理文字、圖片、影片、程式碼），是 AI 的「大腦」。
- **Vertex AI (平台)：** 是 Google Cloud 旗下的一個**企業級機器學習 (ML) 平台**。它的目的是幫助開發者和企業「管理、部署和擴展」AI 模型。

### 2. 合作方式：Gemini 運行在 Vertex AI 之上

當一個企業想要在自己的產品（例如手機 App、公司內部系統）中使用 Gemini 的能力時，他們通常會透過 Vertex AI 來達成：

- **API 調用：** 開發者透過 Vertex AI 提供的 API 來呼叫 Gemini 模型。
- **模型自定義 (Tuning)：** 企業可以將自己的私有數據導入 Vertex AI，對 Gemini 進行「微調」，讓它更懂該公司的專業知識，而這些數據不會被拿去訓練 Google 的公共模型。
- **落地應用 (RAG)：** 利用 Vertex AI 的工具（如向量搜尋），讓 Gemini 結合企業內部的文檔來回答問題（這稱為檢索增強生成）。
- **安全性與控管：** Vertex AI 提供企業級的資料安全、隱私保護和權限管理，確保使用 Gemini 時符合法規。

### 3. 為什麼不直接用 Gemini，而要透過 Vertex AI？

如果你只是想聊天，你可以直接去 `gemini.google.com`。但如果你是**企業或開發者**，透過 Vertex AI 使用 Gemini 有以下好處：

1. **資料隱私：** 在 Vertex AI 中處理的資料，預設不會被 Google 用來訓練模型。
2. **穩定性：** 提供服務水準協議 (SLA) 保障，適合正式產品環境。
3. **完整工具鏈：** 除了 Gemini，Vertex AI 還有「模型花園」(Model Garden)，你也可以在那裡使用 Llama (Meta) 或其他開源模型。
4. **監控與評估：** 幫助你檢查 Gemini 回答的品質，是否有偏見或錯誤。

### 總結

- **Gemini** 是 Google 提供的**強大 AI 引擎**。
- **Vertex AI** 是 Google Cloud 提供的**開發環境與工具箱**。
- **關係：** Vertex AI 是企業獲取、客製化並部署 Gemini 模型的主要管道。

**一句話總結：** Gemini 提供「智慧」，而 Vertex AI 提供「將這份智慧轉化為商業應用」所需的所有基礎設施。

---

## **使用gemini 問答，gemini cli，VertexAI 呼叫api 得到答案，比較三種模式**

---

這三種模式代表了從「普通用戶」到「開發者」再到「企業級應用」的三種不同維度。以下是針對這三種模式的詳細比較：

---

### 1. Gemini 網頁版問答 (gemini.google.com)

這是最直觀的 **B2C (消費者)** 模式，類似於 ChatGPT 的網頁界面。

- **目標對象：** 一般大眾、學生、辦公室職員、內容創作者。
- **介面：** 圖形化網頁或 App 介面，支援多模態輸入（圖片、文件上傳）。
- **費用：** 免費（Gemini Flash）或 月費訂閱制（Gemini Advanced / Ultra）。
- **資料隱私：** **較低。** 除非特別設定，否則對話內容可能會被 Google 用於改進模型（去識別化後的人工審核）。
- **功能特點：**
    - 整合 Google 生態系（讀取你的雲端硬碟、郵件、地圖、YouTube）。
    - 具備較好的上下文對話管理。
    - 無法進行大規模批次處理或程式整合。

---

### 2. Gemini CLI (命令行工具)

這通常是透過開源工具（如 `gemini-cli`）或 Google 提供的 SDK 自行封裝的 **開發者測試** 模式。

- **目標對象：** 工程師、系統管理員、喜歡自動化的 Geek。
- **介面：** 終端機 (Terminal)。通常底層呼叫的是 **Google AI Studio (Gemini API)**。
- **費用：** 根據 API 使用量計費。目前 Google AI Studio 提供相當優渥的 **免費額度**（有頻率限制）。
- **資料隱私：** 中等。API 使用規範通常優於網頁版，但仍需視 API 條款而定。
- **功能特點：**
    - **腳本化：** 可以串接到 Shell 腳本中，批次處理本地檔案。
    - **效率：** 適合快速測試 Prompt 或在伺服器環境中進行簡單問答。
    - **侷限：** 適合單兵作戰，不適合部署為大規模商用服務。

---

### 3. Vertex AI 呼叫 API (Google Cloud)

這是 **B2B (企業級)** 模式，是 Google Cloud 平台的一部份。

- **目標對象：** 企業開發團隊、數據科學家、需要將 AI 整合進 App 的公司。
- **介面：** 程式碼 (Python, Node.js, Go, Java) 或 REST API。
- **費用：** **隨收隨付 (Pay-as-you-go)**。按 Token 數量計費，無免費版（僅有新帳號試用金）。
- **資料隱私：** **最高（企業級保護）。** Google 承諾 **「您的數據不會被用來訓練其底層模型」**，且符合 SOC2, ISO 等合規認證。
- **功能特點：**
    - **完整生命週期管理：** 包含微調 (Tuning)、模型評估、自動化部署 (MLOps)。
    - **高可用性：** 提供服務水準協議 (SLA) 保障穩定性。
    - **整合性：** 輕易結合雲端資料庫（BigQuery）、搜尋引擎（Vertex AI Search）實踐 RAG。
    - **安全控管：** 支援 IAM 權限控管、VPC 網路安全限制。

---

### 綜合對照表

<table id="bkmrk-%E7%89%B9%E6%80%A7-gemini-%E7%B6%B2%E9%A0%81%E7%89%88-gemini"><tbody><tr><th align="left">特性</th><th align="left">Gemini 網頁版</th><th align="left">Gemini CLI (AI Studio)</th><th align="left">Vertex AI API</th></tr><tr><td align="left">**主要用途**</td><td align="left">日常對話、輔助辦公</td><td align="left">開發測試、個人自動化</td><td align="left">企業產品開發、商業化部署</td></tr><tr><td align="left">**操作難度**</td><td align="left">極低（會打字即可）</td><td align="left">中等（需配置環境與 API Key）</td><td align="left">高（需具備程式開發與雲端知識）</td></tr><tr><td align="left">**隱私安全性**</td><td align="left">低（資料可能被拿去訓練）</td><td align="left">中（開發者條款保護）</td><td align="left">**極高（企業級隱私，不訓練）**</td></tr><tr><td align="left">**自定義能力**</td><td align="left">低（僅能調整 System Instruction）</td><td align="left">中（可調整參數如 Temperature）</td><td align="left">**極高（可微調模型、串接 RAG）**</td></tr><tr><td align="left">**穩定性/SLA**</td><td align="left">無保障</td><td align="left">無保障（可能隨時調整頻率限制）</td><td align="left">**有 SLA 保障**</td></tr><tr><td align="left">**費用**</td><td align="left">免費 或 月費制</td><td align="left">免費限額 或 隨量計費</td><td align="left">**隨量計費（企業預算）**</td></tr></tbody></table>

---

### 我該選哪一個？

1. **如果你想寫作業、寫 Email、整理雜亂的筆記：** 請用 **Gemini 網頁版**。
2. **如果你是工程師，想寫個小程式自動整理本地 100 個文字檔：** 請用 **Gemini CLI (或 Google AI Studio API)**。
3. **如果你要開發一個手機 App 給萬名用戶使用，或公司內部要處理敏感個資：** 請務必使用 **Vertex AI**。

---

## gemini 只是模型名稱，有可能改變？

  
所以未來gemini 被淘汰成 GGG 模型，名稱變了，但Vertex AI 仍叫Vertex AI ？

沒錯，這是軟體架構中「平台」與「內容物」的核心差異。

這可以從以下三個層次來理解為什麼 **Vertex AI** 的名稱會比 **Gemini** 更穩定：

### 1. 「貨架」與「商品」的關係

- **Vertex AI 是「貨架（平台）」：** 它是企業用來管理 AI 的一整套基礎設施。
- **Gemini 是「商品（模型）」：** 它是目前貨架上最強的主打產品。
- **未來：** 就算 Google 開發出下一代更強的模型叫 "GGG"，Google 只需要把 GGG 放上 Vertex AI 的「模型花園 (Model Garden)」即可。對於企業客戶來說，他們不需要更換整個平台，只需要把 API 呼叫的路徑從 `gemini-1.5-pro` 改成 `ggg-v1`。

### 2. 歷史已經證明過這點

Google 的 AI 模型名稱換得很快，但平台的名稱相對穩定：

- **以前的模型：** PaLM 2、LaMDA、BERT。
- **現在的模型：** Gemini (Pro, Flash, Ultra)。
- **平台演進：** 早期叫 Cloud AI Platform，後來整合並定名為 **Vertex AI** 後就一直沿用至今。

對企業來說，\*\*「穩定性」\*\*是第一考量。如果 Google 每換一個模型就換一次平台名稱，企業的工程師會瘋掉，因為所有的文件、權限設定和自動化流程都要重寫。

### 3. Vertex AI 裡不只有 Gemini

這也是為什麼 Vertex AI 不會輕易改名最重要的原因。在 Vertex AI 的「模型花園」裡，除了 Google 自家的 Gemini，還提供：

- **Llama** (Meta 的模型)
- **Claude** (Anthropic 的模型)
- **Mistral** (Mistral AI 的模型)
- **Stable Diffusion** (圖片生成模型)

因為 Vertex AI 是一個\*\*「多模型平台」\*\*，它的名字必須具備包容性。即使 Gemini 以後改名叫 GGG，或是 Google 決定不再開發自己的模型（假設），Vertex AI 依然可以作為一個託管其他公司模型的平台而存在。

### 總結

- **Gemini** 是品牌行銷與 AI 技術的名稱（容易隨技術迭代而改變）。
- **Vertex AI** 是雲端服務與企業解決方案的名稱（為了維護企業信任，通常會保持長期一致）。

**你的直覺很對：**  
如果五年後 Google 推出了 **"GGG"** 模型，你大概率還是會進到 **Vertex AI** 的控制台，點選 **"GGG"** 來使用它。

---

## 【google gen ai SDK】 與 【 vertext ai SDK】差異

簡單來說：**Google Gen AI SDK 是給「個人/開發者快速原型」用的；而 Vertex AI SDK 是給「企業/正式生產環境」用的。**

以下是詳細的差異分析：

---

### 1. 背後運作的平台不同

- **Google Generative AI SDK (`google-generativeai`):**
    - 對接的是 **Google AI Studio**。
    - 這是一個輕量級的開發工具，讓你可以快速拿到 API Key 並開始測試模型。
- **Vertex AI SDK (`google-cloud-aiplatform`):**
    - 對接的是 **Google Cloud Platform (GCP)**。
    - 這是一個企業級的機器學習平台，整合了所有的雲端資源。

### 2. 身份驗證方式 (Authentication)

- **Google Gen AI SDK:**
    - 使用 **API Key**。
    - 非常簡單，就像你用 OpenWeather 或其他小工具一樣，貼上一行 Key 就能動。
- **Vertex AI SDK:**
    - 使用 **IAM (Identity and Access Management)**、**服務帳戶 (Service Account)** 或 **OAuth2**。
    - 你需要安裝 Google Cloud CLI (`gcloud`)，設定專案 ID，處理權限認證。雖然麻煩，但對於大型團隊來說，這才安全。

### 3. 資料隱私與安全

- **Google Gen AI SDK (AI Studio):**
    - 如果你使用的是**免費版**，Google 擁有在去識別化後，使用你的輸入/輸出來改善模型的權利。
    - 適合測試、Demo、不具敏感性的個人專案。
- **Vertex AI SDK:**
    - **預設不會將你的資料用於訓練模型。**
    - 提供企業級的加密、VPC 網路安全限制、以及合規性認證（如 HIPAA, GDPR）。
    - 適合處理公司客戶資料、機密文件。

### 4. 功能廣度

- **Google Gen AI SDK:**
    - 專注在「生產生 AI」本身（聊天、生成圖片、Embedding）。
    - 功能比較單一、純粹。
- **Vertex AI SDK:**
    - 除了生成 AI，它還包含整個 **MLOps 流程**。
    - 你可以進行模型監控（看有沒有胡說八道）、模型比對、自動化流水線 (Pipelines)、部署到專屬端點 (Endpoint)。
    - 支援更多模型（Llama, Claude 等等）。

### 5. 配額與穩定性 (Quota &amp; SLA)

- **Google Gen AI SDK:**
    - 免費額度很高，但**每分鐘請求數 (RPM) 限制較嚴格**。
    - 不保證服務水準 (No SLA)。
- **Vertex AI SDK:**
    - 付費使用，配額可以根據企業需求申請調高。
    - 有 **SLA (Service Level Agreement)** 保證，如果系統掛了，Google 要賠錢或提供補償，這對商業營運至關重要。

---

### 快速比較表

<table id="bkmrk-%E7%89%B9%E6%80%A7-google-gen-ai-sdk"><tbody><tr><th align="left">特性</th><th align="left">Google Gen AI SDK</th><th align="left">Vertex AI SDK</th></tr><tr><td align="left">**主要平台**</td><td align="left">Google AI Studio</td><td align="left">Google Cloud (Vertex AI)</td></tr><tr><td align="left">**驗證方式**</td><td align="left">API Key (簡單)</td><td align="left">IAM / Service Account (嚴謹)</td></tr><tr><td align="left">**上手速度**</td><td align="left">極快 (1 分鐘)</td><td align="left">較慢 (需設定 GCP 專案)</td></tr><tr><td align="left">**資料隱私**</td><td align="left">免費版可能被用於訓練</td><td align="left">**絕對不會被用於訓練**</td></tr><tr><td align="left">**適用場景**</td><td align="left">個人作品、快速原型、Side Project</td><td align="left">企業級應用、商業產品、處理個資</td></tr><tr><td align="left">**庫名稱 (Python)**</td><td align="left">`google-generativeai`</td><td align="left">`google-cloud-aiplatform`</td></tr></tbody></table>

---

### 我該如何選擇？

- **選 Google Gen AI SDK，如果：**
    
    
    - 你是學生或獨立開發者。
    - 你想在 5 分鐘內寫出一個會說話的機器人 Demo。
    - 你預算有限，想利用免費額度。
- **選 Vertex AI SDK，如果：**
    
    
    - 你正在為公司開發產品。
    - 你的 App 每天會有成千上萬的人使用（需要高併發）。
    - 你需要處理公司內部的敏感文件或客戶隱私。
    - 你需要把 AI 跟 BigQuery、Cloud Storage 等 Google 雲端服務串聯。

**一句話總結：**  
**Google Gen AI SDK** 是進入 Gemini 世界的**快速捷徑**；而 **Vertex AI SDK** 是在 Google 雲端蓋大樓的**標準地基**。

---

## <span aria-level="2" class="devsite-heading" role="heading">單獨使用 Gemini API 與 Vertex AI 的差異</span><button aria-label="複製這個部分的連結：單獨使用 Gemini API 與 Vertex AI 的差異" class="devsite-heading-link button-flat material-icons" data-id="google-ai" data-title="複製這個部分的連結：單獨使用 Gemini API 與 Vertex AI 的差異" type="button"></button>

下表摘要列出 Gemini API 和 Vertex AI 的主要差異，協助您決定哪個選項適合您的用途：

[https://docs.cloud.google.com/vertex-ai/generative-ai/docs/migrate/migrate-google-ai?hl=zh-tw](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/migrate/migrate-google-ai?hl=zh-tw)

<div id="bkmrk-%E5%8A%9F%E8%83%BD-gemini-api-vertex"><table><tbody><tr><th width="20%">**功能**</th><th>**Gemini API**</th><th>**Vertex AI**</th></tr><tr><th>端點名稱</th><td>`generativelanguage.<wbr></wbr>googleapis.<wbr></wbr>com`</td><td>`aiplatform.<wbr></wbr>googleapis.<wbr></wbr>com`</td></tr><tr><th>註冊</th><td>Google 帳戶</td><td>Google Cloud 帳戶 (須同意條款並提供帳單資訊)</td></tr><tr><th>驗證</th><td>API 金鑰或 [OAuth](https://ai.google.dev/gemini-api/docs/oauth?hl=zh-tw) (如已連結至 Google Cloud 專案)</td><td>Google Cloud 服務帳戶</td></tr><tr><th>使用者介面遊樂場</th><td>Google AI Studio</td><td>Vertex AI Studio</td></tr><tr><td>API 和 SDK</td><td>伺服器和行動/網頁用戶端 SDK - 伺服器：Python、Node.js、Go、Dart、ABAP
- 行動/網路用戶端 (透過 [Firebase AI Logic](https://firebase.google.com/docs/ai-logic?hl=zh-tw))： Android (Kotlin/Java)、Swift、網路、Flutter 和 Unity

</td><td>伺服器和行動/網頁用戶端 SDK - 伺服器：Python、Node.js、Go、Java、ABAP
- 行動/網路用戶端 (透過 [Firebase AI Logic](https://firebase.google.com/docs/ai-logic?hl=zh-tw))： Android (Kotlin/Java)、Swift、網路、Flutter 和 Unity

</td></tr><tr><td>免費使用 API 和 SDK</td><td>是，[視情況而定](https://ai.google.dev/gemini-api/docs/billing?hl=zh-tw#is-Gemini-free-in-EEA-UK-CH)</td><td>新使用者可享 $300 美元的抵免額 Google Cloud</td></tr><tr><td>配額 (每分鐘要求數)</td><td>視型號和定價方案而定 (請參閱[詳細資訊](https://ai.google.dev/pricing?hl=zh-tw))</td><td>因型號和地區而異 (請參閱[詳細資訊](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/quotas?hl=zh-tw))</td></tr><tr><td>企業支援</td><td>否</td><td>- 客戶加密金鑰
- 虛擬私有雲
- 資料落地
- 資料存取透明化控管機制
- 可擴充的應用程式託管基礎架構
- 資料庫和資料儲存空間

</td></tr><tr><td>MLOps</td><td>否</td><td>Vertex AI 上的完整 MLOps (例如：模型評估、模型監控、模型登錄)</td></tr></tbody></table>

</div>

# 【OpenClaw】相關

- [ OpenClaw 太強了，磨合兩週後我的 AI 員工開始半夜自己加班了｜14 個排程完整公開ClawdBot）](https://www.youtube.com/watch?v=OB6o29I78rY " OpenClaw 太強了，磨合兩週後我的 AI 員工開始半夜自己加班了｜14 個排程完整公開ClawdBot）")
- [最新！OpenClaw 手把手安全部屬教學 | OpenClaw / MiniMax M2.5 / Zeabur](https://www.youtube.com/watch?v=aEXR8tPesKk)

# 【opencode】相關

[https://opencode.ai/docs/](https://opencode.ai/docs/)

# 【Repomix】 相關

[https://repomix.com/zh-tw/](https://repomix.com/zh-tw/)

[https://repomix.com/zh-tw/guide/](https://repomix.com/zh-tw/guide/)