Skip to main content

· 17 min read

適用情境:

  • 已知 APISIX 某個 Pod 的 container 發生重啟。
  • 主要可用資料為 Prometheus metrics 與 Kibana logs。
  • 目標是區分「直接原因、觸發原因、放大因素」,而不是只記錄 CPU 或 latency 很高。

先說結論

調查時必須區分:

直接原因:是什麼機制讓 container 結束?

觸發原因:什麼事件觸發了這個機制?

放大因素:哪些限制或設定讓事件惡化?

例如:

直接原因:Liveness probe 連續失敗,kubelet 重啟 container
觸發原因:Route B 流量暴增,使 worker 無法及時回應 probe
放大因素:CPU limit 過低並發生明顯 throttling

CPU、latency、connection 都是調查訊號,不應單獨當成根因。


STEP 0:確認觀測資料可用

不同 APISIX、kube-state-metrics 與監控設定,可能具有不同的 metric labels。執行後續查詢前,先確認:

  • APISIX 與 kube-state-metrics 版本。
  • Prometheus scrape interval。
  • APISIX metrics 是否具有 podnamespacecluster 等額外 labels。
  • Kibana 中 access log 與 error log 的 index/data view。
  • request_timerequest_lengthbytes_sent 是否被解析成 numeric fields。
  • 是否有 Kubernetes events、kubelet logs 或 probe metrics。

先檢查 APISIX metric 的實際 labels:

count by(cluster, job, namespace, pod, instance)(
apisix_http_status
)

APISIX 原生 metrics 不保證包含 podnamespace。這些 labels 通常由 Prometheus service discovery 或 relabeling 加入。若環境沒有 pod label,後面的查詢需改用實際存在的 instance 或其他 target label。

查案順序

鎖定重啟時間 T

確認 container 的終止方式

排除 rollout、probe、node 等外部事件

檢查 CPU、memory 與 throttling

判斷單 Pod 或全域異常

定位 Route、Client、Request 與 Upstream

建立時間線並評估證據強度

不要一開始就從 Route latency 猜根因。


STEP 1:鎖定重啟時間

1.1 確認觀察區間內是否重啟

increase(
kube_pod_container_status_restarts_total{
namespace="$namespace",
pod="$pod",
container="$container"
}[10m]
)

kube_pod_container_status_restarts_total 是 counter。查詢結果大於 0,代表這段時間內有 restart;實際結果可能因 Prometheus 外插而不是整數。

若只是想在圖表上觀察數值發生過幾次變化,也可以使用:

changes(
kube_pod_container_status_restarts_total{
namespace="$namespace",
pod="$pod",
container="$container"
}[10m]
)

但 counter 或 time series 重建時,changes() 也可能把 reset 計為一次變化。

1.2 取得最後終止時間

若 kube-state-metrics 版本有提供此實驗性指標:

kube_pod_container_status_last_terminated_timestamp{
namespace="$namespace",
pod="$pod",
container="$container"
}

其值為 Unix timestamp。將最後終止時間記為 T,後續 Prometheus 與 Kibana 固定查看:

T-15m ~ T+5m

若沒有 timestamp metric,可從 restart counter 的變化點、告警時間或 container 啟動時間反推:

kube_pod_container_state_started{
namespace="$namespace",
pod="$pod",
container="$container"
}

STEP 2:確認 container 為什麼結束

2.1 Last Termination Reason

T 附近查詢:

kube_pod_container_status_last_terminated_reason{
namespace="$namespace",
pod="$pod",
container="$container"
} == 1

查詢結果中的 reason label 才是要判讀的值。

Reason可以確認的事下一步
OOMKilledContainer 因 OOM 被終止檢查 memory limit、working set 與流量/payload
ErrorProcess 以錯誤狀態結束檢查 exit code 與 APISIX error log
CompletedProcess 正常結束對長期運行的 APISIX 仍需查明為何退出
ContainerCannotRunContainer 無法啟動檢查 image、entrypoint、mount 與權限

不要用 max_over_time(...[1h]) 判斷「最後一次 reason」。它可能保留區間內出現過的多個 reason series,無法保證與本次重啟一一對應。

2.2 Last Exit Code

同樣在 T 附近查詢:

kube_pod_container_status_last_terminated_exitcode{
namespace="$namespace",
pod="$pod",
container="$container"
}

不要使用 max_over_time(exitcode[1h])。它取得的是區間內數值最大的 exit code,不是時間上最後一筆 exit code。

Exit Code可以確認的事不能直接確認的事
137通常表示收到 SIGKILL不等於一定 OOM,需搭配 termination reason
143通常表示收到 SIGTERM不代表一定是正常部署
139通常表示收到 SIGSEGV仍需 error log 或 core dump 證明 crash 位置
1Process 非零結束需從 log 找實際 application error
0Process 正常結束碼不代表 APISIX 的退出符合預期

2.3 排除 Kubernetes/平台事件

即使 APISIX 沒有 application error,container 仍可能因下列事件重啟:

  • Deployment rollout 或 image 更新。
  • Scale down、Pod deletion 或手動操作。
  • Node drain、shutdown、NotReady 或 eviction。
  • Liveness probe failure。
  • Pod termination grace period 到期後被強制 SIGKILL
  • ConfigMap/Secret/sidecar 行為造成 Pod 替換。

若 Kibana 有收 Kubernetes events 或 kubelet logs,應搜尋:

kubernetes.pod.name:"$pod" and
message:(
"Killing" or
"Unhealthy" or
"Liveness probe failed" or
"Evicted" or
"Preempting"
)

若只有 APISIX access/error log,沒有 Kubernetes event、kubelet log 或 probe metric,通常無法確認是誰觸發了 SIGTERMSIGKILL。此時應把結論標為 UnknownPossible


STEP 3:確認是否為資源問題

3.1 CPU 使用量

sum(
rate(
container_cpu_usage_seconds_total{
namespace="$namespace",
pod="$pod",
container="$container"
}[5m]
)
)

結果單位為 CPU cores,例如 1.5 代表平均使用約 1.5 cores。

3.2 CPU Limit 使用率

100 *
sum(
rate(
container_cpu_usage_seconds_total{
namespace="$namespace",
pod="$pod",
container="$container"
}[5m]
)
)
/
sum(
kube_pod_container_resource_limits{
namespace="$namespace",
pod="$pod",
container="$container",
resource="cpu",
unit="core"
}
)

若查不到 limit series,可能代表 container 沒有設定 CPU limit,此時不能計算百分比。

CPU 使用率高本身不會直接讓 container 重啟,但可能造成 probe timeout、latency 上升或 worker 無法及時處理請求。

3.3 CPU Throttled Periods

100 *
sum(
rate(
container_cpu_cfs_throttled_periods_total{
namespace="$namespace",
pod="$pod",
container="$container"
}[5m]
)
)
/
sum(
rate(
container_cpu_cfs_periods_total{
namespace="$namespace",
pod="$pod",
container="$container"
}[5m]
)
)

這是「發生 throttling 的 CFS periods 比例」,不是損失的 CPU 百分比。

同時觀察實際 throttled time:

sum(
rate(
container_cpu_cfs_throttled_seconds_total{
namespace="$namespace",
pod="$pod",
container="$container"
}[5m]
)
)

判讀時應同時看:

  • CPU usage 是否接近 limit。
  • Throttled periods 是否持續偏高。
  • Throttled seconds 是否同步增加。
  • Probe failure 或 latency 是否發生在 throttling 之後。

3.4 Memory Working Set

sum(
container_memory_working_set_bytes{
namespace="$namespace",
pod="$pod",
container="$container"
}
)

3.5 Memory Limit 使用率

100 *
sum(
container_memory_working_set_bytes{
namespace="$namespace",
pod="$pod",
container="$container"
}
)
/
sum(
kube_pod_container_resource_limits{
namespace="$namespace",
pod="$pod",
container="$container",
resource="memory",
unit="byte"
}
)

Memory working set 接近 limit 只代表 OOM 風險提高。要確認 container OOM,仍應以 reason="OOMKilled"、kernel/kubelet log 或 OOM event 為直接證據。

另外,重啟當下 memory 突然下降通常是重啟的結果,不應直接解讀為 memory 問題的原因。


STEP 4:判斷單 Pod 或全域異常

以下範例假設 APISIX Pod 名稱符合 apisix.*,且 metrics 具有 podnamespace labels。請依實際環境調整 selector。

4.1 CPU by Pod

sum by(pod)(
rate(
container_cpu_usage_seconds_total{
namespace="$namespace",
pod=~"apisix.*",
container="$container"
}[5m]
)
)

4.2 RPS by Pod

sum by(pod)(
rate(
apisix_http_status{
namespace="$namespace",
pod=~"apisix.*"
}[5m]
)
)

4.3 判讀方式

觀察初步假設還要驗證
單 Pod CPU 高流量不均、Pod local state、worker/plugin 或 node 問題Pod RPS、connections、node resource
全 Pod CPU 高全域流量、共用 plugin 或設定變更Route RPS、change event、plugin dependency
全 Pod upstream latency 高共用 backend 或網路路徑異常upstream_addr、不同 backend、健康檢查
單 Pod latency 高Pod、Node 或 connection pool 問題同 Route 跨 Pod 比較

這些現象只能形成假設,不能直接當成根因。


STEP 5:定位流量來源

5.1 Pod RPS

sum(
rate(
apisix_http_status{
namespace="$namespace",
pod="$pod"
}[5m]
)
)

5.2 Route RPS

topk(
10,
sum by(route)(
rate(
apisix_http_status{
namespace="$namespace",
pod="$pod"
}[5m]
)
)
)

Top 10 只能找到流量最大者,不能直接找到異常者。調查時應比較:

  • 事件前後。
  • 前一小時的相同長度區間。
  • 昨日或過去七天的相同時段。
  • 同 Route 在其他 Pod 的表現。

例如,找出相較一小時前增加最多的 Route:

topk(
10,
sum by(route)(
rate(
apisix_http_status{
namespace="$namespace",
pod="$pod"
}[5m]
)
)
-
sum by(route)(
rate(
apisix_http_status{
namespace="$namespace",
pod="$pod"
}[5m] offset 1h
)
)
)

offset 1h 只是一個方便的比較基準,不一定代表正常 baseline。週期性明顯的服務應比較昨日或過去多日的相同時段。

5.3 HTTP Status

先看完整分布:

sum by(route, code)(
rate(
apisix_http_status{
namespace="$namespace",
pod="$pod"
}[5m]
)
)

再聚焦錯誤與限流:

sum by(route, code)(
rate(
apisix_http_status{
namespace="$namespace",
pod="$pod",
code=~"429|4..|5.."
}[5m]
)
)

重點觀察:

  • 429:限流或 retry amplification。
  • 499:client 在 response 完成前關閉連線。
  • 500:APISIX、plugin 或 upstream application error。
  • 502503504:upstream connection、availability 或 timeout。

流量暴增也可能全部回傳 2xx,因此不能只查 4xx5xx


STEP 6:拆解 Latency

APISIX 的 latency histogram 單位為 milliseconds:

histogram_quantile(
0.99,
sum by(le, type, route)(
rate(
apisix_http_latency_bucket{
namespace="$namespace",
pod="$pod"
}[5m]
)
)
)

Latency type 定義

Type定義調查方向
requestClient 到 APISIX 再回到 client 的端到端時間整體 request path
upstream等待 upstream response 的時間Backend、DNS、網路、upstream connection
apisixrequest - upstreamDownstream 傳輸、NGINX/APISIX processing、plugin

apisix latency 不只代表 Lua plugin 或 CPU。它也包含 downstream/client 傳輸及 NGINX 的非 upstream 時間。

判讀原則:

  • upstream 上升:優先檢查 backend 與 APISIX 到 backend 的網路路徑。
  • apisix 上升:檢查 CPU throttling、plugin、slow client、大 request/response。
  • request 上升但 upstream 正常:較偏向 downstream 或 APISIX 的非 upstream 部分。
  • p99 上升時必須同時看 RPS/sample count;低流量 Route 的 p99 容易抖動。

STEP 7:檢查 Connection

只加總代表目前連線狀態的 series,避免把 acceptedhandled 等累積值混入:

sum by(state)(
apisix_nginx_http_current_connections{
namespace="$namespace",
pod="$pod",
state=~"active|reading|writing|waiting"
}
)
現象可能代表驗證方式
active流量增加、長連線或 request 堆積RPS、request latency、upstream latency
readingClient 傳送較慢或 request 較大request_length、client IP、method
writingClient 接收較慢或 response 較大bytes_sent、request time
waitingKeepalive idle connectionsRPS、keepalive 設定、連線上限

Connection state 是線索,不是大 request/response 或 request 堆積的直接證據。


STEP 8:使用 Kibana 驗證假設

8.1 固定 Pod 與時間

kubernetes.pod.name:"$pod"

時間範圍固定為:

T-15m ~ T+5m

若 APISIX access log 與 error log 位於不同 data view,應分別查詢。

8.2 Route 與 Request

依實際 log schema 查看:

route_id
route_name
uri
method
status
upstream_addr

建立 Top values 或聚合,確認:

  • 哪個 Route 的 request count 增加最多。
  • 哪些 URI/method 與高 latency 或錯誤同時出現。
  • 問題是否集中於特定 upstream_addr

8.3 Client

查看:

client_ip
x_forwarded_for
consumer
user_agent

注意:只有在可信任的 proxy 會覆寫 X-Forwarded-For 時,才應把它當成 client identity。否則該 header 可能被 client 偽造。

8.4 慢 Request

標準 NGINX $request_time 單位是秒,精度到毫秒。若 Kibana field 為 numeric:

kubernetes.pod.name:"$pod" and request_time > 1

代表 request time 大於 1 秒。

查看:

request_time
upstream_response_time
status
route_id
uri
upstream_addr
client_ip

request_time 被 ingest pipeline 轉為 milliseconds,門檻才應使用 1000。文件與 dashboard 必須標明實際單位。

8.5 大 Request

例如搜尋大於 1 MiB 的 request:

kubernetes.pod.name:"$pod" and request_length > 1048576

8.6 大 Response

例如搜尋大於 10 MiB 的 response:

kubernetes.pod.name:"$pod" and bytes_sent > 10485760

以上門檻只是範例,應依服務的正常分布調整。最好同時比較 p50、p95、p99 與事件前後的分布,而不是只用單一固定門檻。

8.7 Error Log

kubernetes.pod.name:"$pod" and
message:(
"worker" or
"signal" or
"segmentation" or
"lua" or
"memory" or
"worker_connections" or
"too many open files" or
"upstream timed out"
)

特別注意:

  • Worker exit/signal。
  • Segmentation fault。
  • Lua exception。
  • worker_connections are not enough
  • too many open files
  • Upstream connect/read timeout。
  • Logger plugin queue 或 shared dict 錯誤。

STEP 9:建立事件時間線

固定整理每個訊號第一次明顯偏離 baseline 的時間:

時間觀察證據來源初步解釋
T-120sRoute B RPS 上升 4 倍Prometheus可能的觸發事件
T-90sActive connections 上升Prometheus流量或 request duration 增加
T-60sCPU 接近 limitPrometheus資源壓力
T-55sThrottled seconds 上升PrometheusCPU quota 開始產生影響
T-40sp99 latency 上升Prometheus使用者可見影響
T-10sLiveness probe failedEvent/kubelet log直接重啟機制
TContainer terminatedkube-state-metrics重啟時間

必須回答:

  1. 哪個訊號最早出現?
  2. 它是否有合理機制導致後續事件?
  3. 是否有其他假設也能解釋相同現象?
  4. 有沒有直接證據或反證?

最早出現的事件不一定是根因;時間先後只能建立因果假設,仍需搭配機制與其他證據。


STEP 10:結案格式

10.1 調查結論

分類結論證據反證/資料缺口信心
直接原因例:Liveness probe failure 後由 kubelet 重啟Probe event、termination timestamp無 kubelet logHighly Likely
觸發原因例:Route B 流量暴增RPS 在 CPU 前 60 秒上升尚未重播 requestHighly Likely
放大因素例:CPU limit 過低CPU 接近 limit 且 throttled time 上升尚未壓測新 limitPossible

10.2 信心程度

等級定義
Confirmed有直接 lifecycle、event、log、core dump 或可重現證據
Highly Likely時間線與機制一致,且沒有主要反證
Possible只有相關性,或仍存在同樣合理的替代解釋
Unknown現有 observability 無法判定

10.3 後續行動

結案時至少列出:

  • 立即緩解措施。
  • 永久修正措施。
  • 需要新增的 metrics、logs 或 alerts。
  • 驗證修正的方法與成功條件。
  • Owner 與預定完成時間。

查案 Checklist

事件與 lifecycle

  • 確認 APISIX metrics 的 labels 與 log schema
  • 鎖定 restart 時間 T
  • Termination reason
  • Exit code
  • Rollout/scale/Pod deletion
  • Liveness probe
  • Node/eviction/kubelet event

資源與影響範圍

  • CPU usage 與 CPU limit
  • CPU throttled periods 與 throttled seconds
  • Memory working set 與 memory limit
  • 是否只有單一 Pod
  • Pod RPS 是否分配不均

流量與依賴

  • Route RPS 與 baseline 增幅
  • HTTP status(包含 2xx429
  • Request/upstream/APISIX latency
  • Connection states
  • Upstream address/backend

Logs 與結案

  • Kibana Route/URI/method
  • Kibana Client/Consumer
  • 大 request/response
  • APISIX error log
  • 建立事件時間線
  • 寫出直接原因
  • 寫出觸發原因
  • 寫出放大因素
  • 記錄反證與資料缺口
  • 標示信心程度
  • 建立後續行動

參考資料

· 3 min read

近期因為公司專案部份功能進行重構,再加上之前公司的專案內並沒有定義 Desgin Guide,前端在實做各元件時都是直接照設計稿上的樣式去填值,也因此常常遇到要改一個顏色需要全盤修改與盤點,非常耗時。因此在這次要進行重構時,與設計討論要將 Design Token 概念導入。

由於目前公司設計稿是使用 Figma 來設計,因此我們是用 Tokens Studio for Figma 這個在 Figma 上的 Plugin。

Figma

在 Figma 上使用 Tokens Studio for Figma,整體上要設定並不困難,同時又可以與如 Github 之類的 remote repository 連動,對於要讓設計給完後同步給前端來說很方便。較須注意注意的我覺得是命名方式。

To RD

在如何將 Token 轉換成 RD 程式上的邏輯,我覺得是較麻煩的部份。我們是使用 Style Dictinoary 和 sd-transforms 這兩個套件來完成。由於我們的專案是使用目前最新的 Tailwind 4.0,目前在相關的 formatter 上面是沒有找到,因此選擇自己完成 formatter。

在 formatter 轉換邏輯上,就需要了解 Token Studio 上的命名模式了,除非要自己做大量的 mapping,不然好的命名方式對於在將 token 轉成 RD 程式上的邏輯會是比較輕鬆的。

結論

目前自己公司在 token 的使用上事先針對 web 端來使用,但在 android 和 iOS 同樣可以使用此方法,這也是後續可以在嘗試的地方。

· 2 min read

package.json 內定義了我們專案中會使用到的套件,需使用的語法,或是專案中的一些詳細設定。

package.json 內容的套件版本

通常我們在安裝專案所需要用到的套件時,套件的前面都會出現^的符號。

  "dependencies": {
"urijs": "^1.19.7"
}

這個符號一般是用來讓我們安裝套裝套件時,讓npm知道我們在套裝這個套件時,可以安裝大版號相同的最新版本套件。

舉例來說,我們現在安裝一個urijs套件,目前最新版本是1.19.7,假設今天出了新的版本叫做1.19.8,那我們在重新安裝這個專案的套件時就會安裝1.19.8。

但這樣,似乎代表不能指定要安裝的版本號。

指定版本安裝方式

在npm 5後,新增了一個叫做package-lock.json的檔案,簡單來說,這個檔案會嚴格定義各個套件所使用的版本。

· 6 min read

Prisma

Prisma 是一個 Node.js 的 ORM 框架,基本上他可以用在任何的 Node.js 框架上。而自己之所以會接觸到也是因為公司的小專案上(使用 Next.js)需要使用到 DB,但又懶得自己去處理一些 SQL injection 的問題,因此就找到了此 ORM 框架。

那 Prisma 基本上是由三個工具組成

  • Prisma Client: Auto-generated and type-safe query builder for Node.js & TypeScript
  • Prisma Migrate: Declarative data modeling & migration system
  • Prisma Studio: GUI to view and edit data in your database

那基本上我們的重點會是在Prisma ClientPrisma Migrate上,Prisma Studio可以想像成我們平常在用來看 DB 的 GUI 工具,所以要裝不裝都沒差。

note

官方的詳細介紹:連結

Prisma Migrate

在講 Migrate 之前要先說到Prisma Schema,簡單來說他就是定義 Prisma 的設定檔,裡面大致上分為三塊。

  • Data sources: DB 的來源與類型。(目前 Prisma 主要可以使用關連式資料庫,至於像 MongoDB 這種 NoSQL 資料庫,官方有說到資源並不全面)
  • Generators: 生成哪些客戶端可以使用。我自己只有使用prisma-client,其他假如你是使用 nestjs 這種框架,可以參考看看。
  • Data model definition: 定義 DB Table 的一些欄位資料和表之間的關聯。
note

Generators:這部分我自己只有使用Prisma Client,如果是使用 nestjs 或需要用到 GraphQL 的人,可以參考連結其他的 generators。

Data sources

datasource db {
provider = "postgresql"
url = "postgresql://johndoe:mypassword@localhost:5432/mydb?schema=public"
}

基本上 provider 就是定義是用哪個 DB,URL 則是 DB 的位置。

在官方的文件中,可能會看到 url 後面是寫env("DATABASE_URL"),基本上你要把 DB 位置寫在環境變數的檔案中或直接寫死在設定檔都可以。

Generators

generator client {
provider = "prisma-client-js"
}

一般來說,如果你沒有要特別使用其他的 generator,就都是使用prisma-client-js

若有其他特殊需求,可參考上方備註。

Data model definition

model User {
id Int @id @default(autoincrement())
email String @unique
name String?
role Role @default(USER)
posts Post[]
profile Profile?
}

model Profile {
id Int @id @default(autoincrement())
bio String
user User @relation(fields: [userId], references: [id])
userId Int
}

model Post {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
title String
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
categories Category[] @relation(references: [id])
}

model Category {
id Int @id @default(autoincrement())
name String
posts Post[] @relation(references: [id])
}

enum Role {
USER
ADMIN
}

講白了,基本上就只是你 Table Colume 的定義內容和 Table 之間的關聯。哪個欄位是 PK、哪個欄位有 Default 的資料或哪個欄位是 NOT NULL,在model <Table name>裡面都會定義出來。但相對的,要怎麼去寫這些定義,就需要去查官方文件了。

所以,基本上我都是先在 db 建好 table,定義好 colume 的格式,再透過

npx prisma db pull

這個指令,讓 prisma 幫我產好 model。

假設在 db 設計 table 時,table 沒有 PK 等之類的問題,再透過上方的 shell 去執行後,prisma 都會有 warning 或 error 的提示,可以讓我們順便知道在設計 table 時有沒有犯了哪些根本問題。

note

有關 Prisma 的 model 要怎麼寫,可以參考官方的文件連結

我自己是習慣使用npx prisma db pull幫我產好 model XDD

Prisma Client

白話的說法就是你可以透過上方定義的 generator,透過 JavaScript 之類的方式,開始去跟 DB 進行互動。

使用的方式首先是將 generator 給引入,要使用 CJS 還是 ESM 的方式引入都可以。

const { PrismaClient } = require("@prisma/client");

const prisma = new PrismaClient();

CRUD

const user = await prisma.user.create({
data: {
email: "elsa@prisma.io",
name: "Elsa Prisma",
},
});

其寫法簡單來說就是

<引入的prisma client變數>.<prisma model name>.<行為(CRUD)>({
<where 條件>,
<寫入的資料>.....
})

行為有像是單筆新增的create,多筆新增的createMany,查詢的findUniquefindFirstfindMany......。

note

這邊不多做使用說明,因為小弟覺得 ORM 的東西看文件才能比較系統性的了解其語法。 參考:

結語

上方簡單說明了 Prisma 這個 ORM 框架。像 Prisma Client 我們也只有簡略的介紹了 CRUD 的語法,但他其實還有像 Middleware 和 log 等較進階的東西。若對 Prisma 有興趣,可以在去閱讀其官方文件。

· 2 min read

Git config 是定義了 Git 環境的設定檔,這些檔案可以被存放在三個地方。

Git Config 環境層級

  1. System 層級的:/etc/gitconfig/

    用於針對所有用戶的設定

    可以透過以下指令來針對 system 層級做設定。

    $ git config --system
  2. 用戶層級的:~/.gitconfig

    用於針對個別使用者的設定

    可以透過以下指令針對用戶層級做設定。

    $ git config --global

    個人是習慣 git 的相關設定會設置於用戶層級的,因此大多設定內容都在~/.gitconfig內。

  3. 專案層級的:/.git/config

    個別專案設定的

    可以透過以下指令針對專案層級做設定。

    $ git config

    大多如 remote 倉庫的位置等資訊都是設置在這個層級的。

在這三個層級裡,專案層級的東西會去覆蓋用戶層級的,而用戶層級的內容會去覆蓋 System 層級的。

Git 規格覆蓋的準則:專案層級 > 使用者層級 > 系統層級

· 3 min read

curl 是一個同 wget 為 Linux 上方便的指令,可把網頁抓下來進行分析。一般來說當工程師撰寫完 API 後,都需要進行 HTTP Request 來測試,目前有像 postman 方便的 GUI tool,但如果懶惰或不想啟動較吃資源的 GRU tool,此時我們就可以透過 curl 指令幫助我們測試。

以下我紀錄個人覺得較常用之指令。

curl GET

crul 預設為使用 GET 請求,一般來說指令組成結構為curl [option] [URL]

下方我們會以 httpbin 作為 HTTP Request 的 URL 並說明常用 option 選項。

補充:httpbin 回傳會以 JSON 格式為內容格式。

curl https://httpbin.org/get
//取得對httpbin進行get的回傳內容

curl -I https://httpbin.org/get
//只需要顯示response header
//其實就等於 curl --head hhttps://httpbin.org/get
//-I為簡寫,可透過curl -help查詢

curl -o abc.txt https://httpbin.org/get
//將httpbin進行get的回傳內容儲存下來,並存在一為abc.txt的檔案內
//小寫"o"為下載請求資源到新的檔案
//檔案之名稱與副檔名可自行設定

curl -O https://httpbin.org/get
//同為將httpbin進行get的回傳內容
//大寫"O"為使用指定網址伺服器的檔名作為下載之檔名

curl -L google.com
//檔網頁進行redirect時,連到redirect網址
//可以試看看沒有加上"-L"的狀況

curl https://httpbin.org/get -H "accept: application/json"
//設定request所要挾帶的header
//這邊設定告知伺服器用戶端可解讀JSON內容

curl POST/PUT/......

除了最基本的 get,可以使用"-X"決定要進行"GET|POST|PUT|DELETE|PATCH"哪個 http method。

在我們使用-X 得時候,我們常常會使用到下列的指令。

-H 夾帶的header
-d 夾帶post data內容
-u 夾帶使用者帳號密碼
-b 攜帶cookie
curl -X POST "https://httpbin.org/post" -d "email=abc@gmail.com" -H "accept: application/json"
//進行post時,夾帶email內容
//要使用其他的http method 更改-X後面的名稱

curl -X POST "https://httpbin.org/post" -b "num=20"
//設定cookie num=20

curl -u "abc:200" https://httpbin.org/get
//若網頁有使用basic auth,可以使用-u夾帶帳號密碼過驗證

· 3 min read

透過 Hostname 來決定使用哪一組帳號的 SSH Key 進行溝通。

SSH 生成

首先我們先使用ssh-keygen指令生成金鑰

圖中我們的指令為ssh-keygen -t rsa -C "email@gamil.com"

  • -t 為加密方法的選擇,我們選擇使用 RSA 加密
  • -C 為註解,會加入 SSH 的金鑰。可做為金鑰持有者的辨識。

輸入上述指令後,會分別詢問

  • 金鑰存放位置和檔案名稱
  • 是否設置 Passphrase(如有輸入,會須重複輸入一次)

上述均輸入完畢後,會產生 id_rsa 和 id_rsa.pub(這邊以預設檔名說明)

  • id_rsa 此為私鑰,也就是要自己保管好的密碼。
  • id_rsa.pub 此為公鑰,也就是對外公開的鑰匙,此會作為與本地端私鑰溝通使用。

將生成的金鑰複製到 Github

如果使用 linux,可以直接使用 ssh-copy-id 複製。

mac 如要使用 ssh-copy-id,需使用 Homebrew 安裝。

  • mac 還可以使用 pbcopy 來複製,指令pbcopy < ~/.ssh/id_rsa.pub

或是去打開檔案複製都可以。


設定 ssh config 方便選擇對應的 git 倉庫

在金鑰該層目錄,新增一 config 檔案。

可以使用nanovim之類編輯都可以,看個人習慣。

nano ~/.ssh/config

新增下列資訊

Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_rsa

Host github-another
HostName github.com
User git
IdentityFile ~/.ssh/id_rsa_new
  • Host 後面的 github.com 或 github-another 就是你要連接倉庫的簡稱。
  • HostName 填 HostNmae 的 domain 或 IP,因為是連 github,所以是 github.com。
  • User 登入 SSH 的 username,個人習慣統一為 git。
  • IdentityFile key 的路徑。 其他還有 Port 或 ForwardX11 等指令。不過我們是連 github,所以不需要。

設定完成後 可以下ssh -T <Host>檢查。 ex: ssh -T github-another


Clone 與 push

  • Clone: git clone <host-in-ssh-config>:<username>/<repo>
  • Push: git remote set-url origin <host-in-ssh-config>:<username>/<repo>

補充

以往只有單一一組的 github,我們會直接在 git 的 global 設定好 username 和 email。

但在有多組的 github 帳號後,如不想均使用相通的名稱。 需先使用git config — global — unset user.name git config — global — unset user.email取消 global 設定。

再根據 Repo 來決定 User 資料

  • git config user.name "userName"
  • git config user.email "eamil"

· 3 min read

Google Apps Script(GAS)是什麼,可以參考wiki的介紹。但我一般會把它解釋成一個後端,類似 nodejs 之類的。

在 GAS 裡面,你可以透過 JavaScript 去連接 Google 的各類服務,或是去連接 Google 的 Firebase 資料庫也是可以的。這邊我們會使用 GAS 來串接 Google Sheets。

GAS 連結 Google 表單

要開啟 GAS 的編輯器,可以從 Google 表單上方的工具列 工具>指令碼編輯器 或是在 Google 雲端硬碟右鍵>更多>Google Apps Script(要先連結 GAS 應用程式),開啟後副檔名應該會是 gs。

目前的 GAS 是可以使用 es6 語法的,但因為要做一些對應的設定,這邊我們會使用較舊的 JavaScript 語法撰寫。

function doPost(e) {
//取得參數
var params = JSON.parse(e.postData.contents);
var num = params.num;
var one = params.one;
var one_other = params.one_other;
var boss_one = params.boss_one;
var to = params.name;
var date = params.date;

//sheet資訊
var SpreadSheet = SpreadsheetApp.openById("");
var Sheet = SpreadSheet.getSheets()[0];


//setValue...
...

return ContentService.createTextOutput(params);
}

上述我們撰寫了一個 doPost 的 function。 doPost 其實就是我們在 Call 這隻 gs 檔的 API,進行 post 時會觸發的 function。

我們可以先透過 e 這個參數取得 post 的資料。 接下來透過SpreadsheetApp.openById("")選擇要開啟哪個 Google 表單的檔案,再透過SpreadSheet.getSheets()[0]綁定好選擇的檔案裡面的哪張表(0 表示第一張表)。

選擇好表後,就可以透過 getRange()取得表的指定格子位置,並透過 setValue()或 setFormula()方法來將值存入。

最後的 return 則是要回傳什麼內容。

GAS 部署

在寫完 GAS 的 code 後,我們要部署並產生 API。 選擇發佈>部署爲網路應用程式,將具有應用程式存取權的使用者改爲 “Anyone, even anonymous“ ,並點選部署。

接下來第一次部署會出現權限核對的一些設定。 基本上就是核對權限>選擇自己的帳戶>進階>前往>允許。 點選完畢後會出現下圖

那串 URL 就是你的 API 路徑。


補充

  • 在 GAS 內沒有 console.log(),要使用 Logger.log()

  • GAS 的 goGet()和 doPost()方法,不能直接 return 一個 object。但可以轉成 JSON 回傳,詳細可參考

· 2 min read

要製作 Discord Bot 目前主要有兩個 API 可以使用。

  • discord.io (官方維護)
  • discord.js (民間版本)

但從 npm 上可以發現目前 discord.io 已經約兩年沒有更新了,如果遇到任何 Bug,要等到官方修復可能需要一定的時間。也因此較推薦使用 discord.js 這個非官方的套件。


製作方法

  • 先去 discord 官方建立一個 bot(可參考 discord.js 上的教學)
  • 把 bot 加入你所要放置的頻道
  • 開始撰寫你的 bot
// 讀取discord.js套件
const Discord = require('discord.js');

// 建立一個Discord client
const bot = new Discord.Client();

// 當你要啟動這個bot時,會執行的事情
bot.on('ready', () => {
console.log('Ready!');
});

// 在建立一個bot時會取得token,這邊要輸入token
bot.login('your-token');

上述輸入完後,就會在你的 Discord 頻道看到 Discord 機器人。 {% asset_img bot.png %} 我這邊因為已經啟動上述的 code,所以 bot 會是線上的狀況,如果沒有執行的話,會顯示離線。

在建立完 bot 之後,接下來就是要開始監聽輸入的內容。

// message裡面會有使用者輸入的內容和使用者資料等等
bot.on('message', message => {
console.log(message.content);
});

我們只需要去監聽使用者輸入的內容做相對應得事情。 例如使用!作為 bot 要回覆得行為偵測, 接下來就可以使用 switch...case 的寫法,來判定!後面的內容 如果!後面的內容並不是定義好的 可以再使用 default 來回覆錯誤訊息。

· 5 min read

App.js 文件配置

//引入第三方middleware package
//引入http-errors套件
var createError = require('http-errors');

//引入express套件
var express = require('express');

//引入path套件
var path = require('path');

//引入cookie-parser套件
//接收到cookie資料做解析
var cookieParser = require('cookie-parser');

//引入morgan套件
//可以記錄各種事件資料
//例如進行get 或 post等行為
var logger = require('morgan');

//連接透過express.Router()產生實例的router
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

var app = express();

//模板引擎 這邊使用pug 要使用ejs將pug換成ejs
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.use(logger('dev'));

//可以透過json或一般的字串取得get post等資料
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
//靜態資料 抓到根目錄/public 資料夾
app.use(express.static(path.join(__dirname, 'public')));

//使用上方引入的./routes/index 來管理根目錄router
app.use('/', indexRouter);
//使用上方引入的./routes/user 來管理user router
app.use('/users', usersRouter);

//如果上方的router都沒進入,抓取錯誤
//透過http-errors套件顯示404錯誤
//也可以自定義404錯誤要顯示的title,這邊title定為"This item is not exist!"
//要顯示其他訊息 將404改成其他http狀態碼
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404, 'This item is not exist!'));
});

//錯誤的處理
//預設的處理方式是僅在開發的過程中提供錯誤訊息
// error handler
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};

// render the error page
res.status(err.status || 500);
res.render('error');
});

module.exports = app;

上述為 express App.js 的默認設定。

但在實際上,express-generator 產生的專案,package.json 內 npm start 實際上是去執行"node ./bin/www",也就是執行 bin 資料夾裡 www 的檔案。下面簡單紀錄 www 文件配置。


bin/www 文件配置

#!/usr/bin/env node

/**
* Module dependencies.
*/
//有載入上述說明的app.js檔設定
var app = require('../app');
var debug = require('debug')('expr:server');
var http = require('http');

/**
* Get port from environment and store in Express.
*/
//設定port 如果環境有預設使用環境預設的,沒有就使用3000 port
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
* Create HTTP server.
*/

var server = http.createServer(app);

/**
* Listen on provided port, on all network interfaces.
*/

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
* Normalize a port into a number, string, or false.
*/

function normalizePort(val) {
var port = parseInt(val, 10);

if (isNaN(port)) {
// named pipe
return val;
}

if (port >= 0) {
// port number
return port;
}

return false;
}

/**
* Event listener for HTTP server "error" event.
*/

function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}

var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port;

// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}

/**
* Event listener for HTTP server "listening" event.
*/

function onListening() {
var addr = server.address();
var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port;
debug('Listening on ' + bind);
}

express router 上個人常用的指令

req -> request

res -> response

  • req.params
    • 取得路徑參數值
    • ex
    app.get('user/:number', function (req, res) {
    var num = req.params.number;
    res.json({ number: num });
    });
  • res.json()
    • 輸出 json 資料
  • res.render()
    • 渲染指定畫面
  • res.redirect()
    • 網址重新導向

其餘大致上是邏輯的處理。


補充

  • req.params. ...
    • 撈出路由設定的資料
  • req.query. ...
    • 取得網址的參數
  • app.use()
    • 使用 middleware
    • 類似一層一層的過濾,中間可以處理 http 的 request、response 或一些檢查動作等等。
    • 參考