# K8s介紹 * k8s是 Kubernetes的簡稱,是一個能協助自動化部屬、擴張、管理app的工具(關於app是甚麼,參考另一篇TSMC紙本筆記,裡面有提到app的開發原則) * 可以將一個超大型的專案分成許多不同的微服務,如此一來當某個服務crash時,便不會直接癱瘓整個系統;而k8s便是負責管理這些微服務的 # Docker介紹 * Docker是一個可以將程式碼封裝的工具,目的是讓這個程式碼能在不同環境下執行 * 通常會將程式碼打包成Docker Image,接著上傳到Docker hub,如此一來便可以方便的下載這個映像檔並隨時執行了 將程式碼dockerize時,需要一個設定檔 ```dockerfile FROM node:6.2.2 # 使用Node.js版本6.2.2的基礎映像 WORKDIR /app # 設定工作目錄為/app,在容器內建立/app目錄 ADD . /app # 將當前目錄下的所有文件和目錄添加到/app目錄中的容器內 RUN npm install # 執行npm install指令,安裝應用程序所需的依賴項 EXPOSE 300 # 指定容器對外開放的端口為300 CMD npm start # 在容器啟動時執行npm start指令 ``` # K8s Pod * Pod是在k8s上運行的最小部屬單位 * 一個pod就相當於一個application * 一個pod中可以包含多個container,而一個container就是一個打包好的Docker Image * 同一個pod 中的container可以透過local port numbers溝通,而不同pod則透過 每個Pod都需要一個.yaml設定檔 ```yaml # my-first-pod.yaml apiVersion: v1 # 使用的Kubernetes API版本 kind: Pod # 定義一個Pod資源 metadata: # Pod的元數據 name: my-pod # 指定Pod的名稱 labels: # 標籤,用於識別和分類Pod app: webserver # 添加一個名為"app",值為"webserver"的標籤 spec: # Pod的規範 containers: # 定義Pod中的容器列表 - name: pod-demo # 容器的名稱 image: zxcvbnius/docker-demo # 使用的映像 ports: # 定義容器的端口配置 - containerPort: 3000 # 指定容器對外開放的端口為3000 ``` # Pod lifecycle * pod 的狀態一定是以下幾個的其中之一 1. Pending: 創建中 2. Running: 已成功被創建,並在其中一個Node上運行 3. Failed: Pod的所有container都已被終止,並至少有一個container是因為錯誤而被終止 4. Succeeded: Pod的所有container都已成功被終止 5. Unknown: 無法取得該Pod的狀態 * 容器探測(container probe)是Pod lifestyle中的一項任務,概念是定期檢查這個Pod中的container是否還能正常運作,詳細內容參考下面的k8s healthcare章節 # K8s Node * Node指的是在k8s上實體運行的一台機器 * 在一個Node上運行多個app,可以妥善利用資源 有了Node的觀念後,便可看看k8s的整體架構 ![](https://hackmd.io/_uploads/Skc59KIt2.png) * master node是整個k8s的控制中心,負責管理和監控整個Kubernetes Cluster(圖上沒畫出) * Load balancer通常由 Node 供應商提供,負責決定要將 request 送給哪個 Node * 每個Node都有屬於自己的iptables,可限制哪些連線可以連進來,並決定決定收到的 request 要交給哪個 Pod * 每個Node都有屬於自己的kubelet,負責管理該Node上的所有 Pod,並與 master node 即時溝通(master node之後會提) * 每個Node都有屬於自己的kube-proxy,負責將目前該 Node 上所有 Pod 的資訊傳給 iptables,以便iptables可以隨時掌握Pod的最新狀態 * 一個白色框代表一個Node * 一個橘色框代表一個Pod,是k8s上的最小運行單位,可以觀察到一個Node可能有不只一個Pod * 一個綠色框代表一個Container(對應到一個Docker image),可以觀察到一個Pod可能有不只一個Container # K8s Deployment * Deployment負責k8s中一切服務的佈署(例如Pod) * Deplayment會自動創建指定數量的Pod replicas(副本)並維護,維護的意思是始終讓Pod replicas的數量保持固定 * Deplayment與Deplayment負責維護的Pod共存亡 * 若有兩個label 相同的Pod1,Pod2,在deployment時在k8s cluster實際運行的Pod為? * 可以使用kubectl set image的方式rollout(升級)這個deployment內Pod的container;升級過程中,會另外生成Pod來取代原本Pod,以實現zero downtime(無停機服務遷移) * 可以使用kubectl rollout undo的方式rollback(回朔)置過去的版本 (補充) Replication的概念是將工作內容相同的Pod多複製幾份,以因應Pod 故障或是大量請求 每個Deployment都需要一個.yaml設定檔 ```yaml # for kubectl versions >= 1.9.0 use apps/v1 apiVersion: apps/v1beta2 kind: Deployment metadata: name: hello-deployment # Deployment的名稱 spec: replicas: 3 # 定義了副本數量(replicas)為3 selector: # 以便選擇屬於這個Deployment的Pod matchLabels: app: my-deployment template: # 定義了Pod的模板(template) metadata: labels: app: my-deployment spec: containers: # 定義了容器(container)的相關設定 - name: my-pod image: zxcvbnius/docker-demo:latest ports: - containerPort: 3000 # 容器將會在Port 3000上監聽連線 ``` * 值得注意的是 * 只有label=matchLabels的那些Pod,才在deployment的管理範圍之內 * deployment會在創建的每個Pod中加上"template中的labels" # k8s service * service的功能是作為Pod與外部溝通的橋樑 每個service都需要一個.yaml設定檔 ```yaml apiVersion: v1 kind: Service metadata: name: hello-service spec: type: NodePort ports: - port: 3000 nodePort: 30390 protocol: TCP targetPort: 3000 selector: app: my-deployment # 將request導入至指定label的Pod上 ``` * port: 建立好的 Service "本身"要以哪個埠號連接到 Pod 上,通常會與targetPort相同 * nodePort:指定節點(Node)上對外開放的埠號,外部想要連結到這個service就是靠這個 * targetPort: 建立好的 Service 要連接到埠號上的哪個port,通常會與targetPort相同 這樣設定後,外部便可透過這個網址與Pod互動了 > http://192.168.99.100:30390 * 192.168.99.100是Cluster ID,每個service都會有一個。沒特別指定的話由系統隨機生成 應用: Blue Green deployment * 概念: 先將 v2 版本部屬到正式環境,經過測試後再把流量從 v1 版本切換至 v2 版本,若發現有問題,可以快速切回v1版本 * 實作: 1. 原本的情況,流量會均勻分攤到三個版本之中 2. 透過設置service的label,可以將流量全部移轉到指定版本 * https://ithelp.ithome.com.tw/articles/10292369 # K8s Label * K8s中的Pod 或Node,可以帶有Label(一對具有辨識度的key/value),以更加清楚的識別這個物件的功用、層級 * 每個Pod 或Node可以擁有0或多個label * 應用1: deployment時能夠透過label找到指定的Pod以佈署 * 應用2: 在Pod的.yaml中新增nodeSelector選項,以指定這個Pod只能被部署於有這個標籤的Node上 # k8s healthcare * 目的是定期送request至Pod中的container,用來偵測這個container是否有正常運作 * 用來送訊息的Probe(探針)有三種類型: 1. livenessProbe: 定期送請求確認,若失敗則會根據設定自動重啟 2. readinessProbe: 定期送請求確認,若失敗則會切斷與這個Container之間的聯繫 3. startupProbe: 只會在這個Container啟動時執行一次,在startupProbe檢測通過前不會啟動其他的Probe,若失敗則會根據設定自動重啟 livenessProbe 範例 修改一下之前的deployment .yaml檔 ```yaml apiVersion: apps/v1beta2 # for kubectl versions >= 1.9.0 use apps/v1 kind: Deployment metadata: name: hello-deployment spec: replicas: 3 selector: matchLabels: app: my-deployment template: metadata: labels: app: my-deployment spec: containers: - name: webapp image: zxcvbnius/docker-demo ports: - name: webapp-port containerPort: 3000 livenessProbe: httpGet: path: / port: webapp-port initialDelaySeconds: 15 periodSeconds: 15 timeoutSeconds: 30 successThreshold: 1 failureThreshold: 3 ``` * 相較於之前deployment的設定,在container中新增了livenessProbe屬性 * 根據範例設定檔,k8s系統將使用http get的方法,在啟動後的15秒(initialDelaySeconds)後,每隔15秒(periodSeconds)檢查一次這個container負責的根目錄是否能正常回應;若連續3次(failureThreshold)都沒在30秒(timeoutSeconds)內收到回應,將重啟這個Container;若連續1次(successThreshold)成功在30秒收到(timeoutSeconds)內收到回應,則被視為正常運行 # k8s secret * 可以創建secret物件以儲存密碼等重要資料 * 使用時,可以將這個物件掛載至環境變數中 先創建一個secret 物件 ```yaml apiVersion: v1 kind: Secret metadata: name: demo-secret-from-yaml type: Opaque data: username: cm9vdA== password: cm9vdHBhc3M= ``` 接著可以在創建Pod時,將secret物件放到這個Pod的環境變數中 ```yaml apiVersion: v1 kind: Pod metadata: name: my-pod labels: app: webserver spec: containers: - name: demo-pod image: zxcvbnius/docker-demo ports: - containerPort: 3000 env: - name: SECRET_USERNAME valueFrom: secretKeyRef: name: demo-secret-from-yaml key: username - name: SECRET_PASSWORD valueFrom: secretKeyRef: name: demo-secret-from-yaml key: password ``` * 注意env的部分 第二種方法是在創建Pod時,將secret物件掛載(mount)到這個Pod的檔案路徑下 ```yaml apiVersion: v1 kind: Pod metadata: name: my-pod-with-mounting-secret labels: app: webserver spec: containers: - name: demo-pod image: zxcvbnius/docker-demo ports: - containerPort: 3000 volumeMounts: - name: secret-volume mountPath: /etc/creds readOnly: true volumes: - name: secret-volume secret: secretName: demo-secret-from-yaml ``` * 注意mountPath (補充:掛載的概念) 先在Pod中設定一個volumes,可以當作是這個Pod中的虛擬硬碟 接著在Container中使用volumeMounts關鍵字決定要掛載的檔案跟掛載的路徑 # k8s Sidecar *若一個pod中包含了不只一個container,可以依照sidecar模式設計,將Container分成 1. Application Container: 負責主要業務邏輯 2. Sidecar Container: 負責擴增及改善主應用容器的功能性,這部分通常須盡量提高通用性 範例: ```yaml ``` # Azure 介紹 * Mirecroft 提供的服務,讓其他人可以付費租用該公司的計算、儲存、網路等資源 * 是全球第二大的雲端運算平台 * Web app的在Azure的開發與部屬屬於Paas(Platform as a service),使用者只需要管理application跟data的儲存即可 # Azure 使用架構 * 要想使用Azure上的資源(如虛擬機、資料庫),需要遵從一定模式存取 ![](https://hackmd.io/_uploads/rywV9Bdth.png) * 上圖為一個Azure帳號存取平台上資源的架構 * Resourse: 代表這個雲端運算平台提供的資源 * Resourse group: 資源組,將資源放到資料夾中,方便組織與管理 * Subscription: 為了使用這些資源,必須"訂閱"這些服務才有辦法使用 * Management groups: 負責管理一個Azure下的許多訂閱 # Istio 介紹 * istio元件支援k8s的架構,最大的用途是對k8s中的流量進行管理與分流;除此之外,還可以透過log觀察並紀錄流量、提供底層的安全通信管道等 * Istio分成兩個主要的部份 * Control Plane: 負責管理和控制,是Istio的核心部分,已安裝在 istio-system Namespace 裡 * Data Plane: 負責實際處理和轉發流量,將會以 Sidecar 模式注入到每一個 Pod之中(Sidecar是Pod中的container,也就是說Istio會在Pod中心曾一個container以完成其工作) 先在電腦上安裝Istio,接著再安裝至k8s中,即可在k8s中使用Istio > kubectl get pods -n istio-system (在k8s中使用Istio範例) # Istio Request Routing * 實作如何用Istio實現"隨心所欲的控制流量" * 需要透過Istio內建的DestinationRule及VirtualService來將流量移轉至特定版本; 首先,使用Istio Gateway的方法引流(像是k8s service的加強版) Gateway 範例: ```yaml apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: my-gateway spec: selector: istio: ingressgateway servers: - hosts: - my-service.example.com port: name: https number: 443 protocol: HTTPS tls: mode: SIMPLE credentialName: my-certs ``` 接著必須配置以下兩個文件,並使用以下指令部屬於系統之上 > kubectl apply -f \<file> DestinationRule(.yaml): ```yaml apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: reviews #定義這個DestinationRule本身的名稱 spec: host: reviews subsets: - name: v1 #目的地v1 labels: #label 為 "version:v1"的所有pod將會被當作目的地v1 version: v1 - name: v2 #目的地v2 labels: version: v2 - name: v3 #目的地v3 labels: version: v3 ``` * 負責定義目的地為何 VirtualService(.yaml): ```yaml apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: reviews spec: hosts: - reviews http: - route: - destination: host: reviews subset: v2 ``` * 負責定義分配規則,可以為 1. Prefix Routing: 透過url 前綴的方式來決定要分配到哪個目的地 2. Weight Routing: 直接指定要分配到各目的地的流量比例分布 3. Header Match Routing: 透過http header來決定要分配到哪個目的地 配置完以上文件後,接著創建Pods並為其加上特定的標籤,即可自由分配流進gateway中的流量 # CI/CD 實作教學 有了k8s跟Azure的概念後,便可以在Azure將一個Web app部屬到K8s上 ## CI CI 必不可少的檔案便是DockerFile,有了這個檔案,便可在CI的流程中將code打包成Docker Image,以便部屬到K8s上 另一個CI 必不可少的檔案便是azure-pipeline.yaml,這個檔案會放在Web app的那個資料夾中,裡面必須包含至少兩個步驟,分別是"Build code"跟"建立Docker Image" CI成功後,會產生一個Artifact,CD時便可拿去使用 ## CD 在CD的過程中,會分成設定Artifact跟Stages兩個部分,其中Artifact便是之前CI產生的那個,而在Stages中,一定要記得選擇Agent job中的Agent pool,代表CD時要用哪個運算資源,除此之外海有一些細節 1. Deployment type 選擇helm 2. Deployment Repository URL選擇Azure Repos中有Deployment.yaml跟Service.yaml的那個 service.yaml 架構 ```yaml apiVersion: v1 kind: Service metadata: name: spec: selector: app: ports: - protocol: TCP port: targetPort: ``` deployment.yaml 架構,包含: 這段 YAML 文件設定了一個 Deployment,其中包含以下內容: * Deployment 名稱 * 規格 (spec): * Selector:選擇 Deployment 相關的 Pod * 副本數 (replicas):設定為 1。 * Pod 的範本 (template): * Pod 的標籤 (labels): * Pod 的註釋 (annotations): * 容器 (containers): * 容器名稱 * 容器映像檔 (image) * 容器拉取策略 (imagePullPolicy) * 資源需求 (resources) * 容器的 Port: * 環境變數 (env) # ELK介紹 參考資料: https://ithelp.ithome.com.tw/articles/10259069 * 由以下三個開源工具組合而成的資料分析平台,可以輕鬆的蒐集、儲存、可視化大數據 1. Elasticsearch: 全文檢索與分析資料,並可以當作no sql類型的database使用 2. Logstash: 轉換資料格式後傳輸到指定的位置 3. Kibana: 可視化資料 完整的流程是: 1. 啟動ELK,啟動filebeat 2. Filebeat讀取log檔並傳給Logstash 3. Logstash接收資料後,分析、過濾資料並轉換資料格式,接著傳給Elasticsearch 4. Elasticsearch接收資料後,將資料儲存至Index中 5. Kibana從Elasticsearch取得資料,並進行資料可視化操作 6. 使用者在瀏覽器中輸入 http://localhost:5601 查看可視化的資料 # 啟動ELK > sudo systemctl start elasticsearch (預設在9200,9300等待請求) > sudo systemctl start logstash (預設在9600,5044等待請求) > sudo systemctl start kibana (預設在5601等待請求) # Filebeat Filebeat的功能是從特定位置抓log下來,接著將資料傳給其他人 簡單的filebeat範例,可以將抓下來的資料傳給kibana可視化,同時傳給Elasticsearch儲存並分析 ```yaml filebeat.inputs: # 設定要抓取log的路徑 - type: filestream enabled: true paths: - /usr/local/var/log/nginx/*.log # 設定連結到kibana的位址 setup.kibana: host: "localhost:5601" # 設定連結到elasticsearch的位址 output.elasticsearch: hosts: ["localhost:9200"] ``` 接著啟動filebeat > ./filebeat -e -c filebeat.yml 除此之外,filebeat還能做到自訂索引、增加自訂的欄位、使用re表達視初步過濾資料等等 # Elasticsearch 介紹 ## 重要名詞解釋 * Node: Elasticsearch在運作時的實體 * Document: Elasticsearch可以處理的資料結構 * Index: Elasticsearch透過Index來讀取或寫入Document * Shard: Node中實際儲存資料的地方,分成Primary(主要)跟Replica(副本) # Kibana介紹 https://ithelp.ithome.com.tw/articles/10266681 Kibana是一個資料分析跟可視化的工具,可以將資料繪製成各式圖表、實時數據分析、搜尋和過濾特定資料等、建立自己的dashboard等等 ## 建立index pattern * index pattern 概念: 在Elasticsearch中資料存在index中,為了要在kibana中取得index 中的資料,必須要在Kibana中設定index pattern,以選擇查看一個或多個存在Elasticsearch index 中的資料 * 舉例來說當index pattern 設定成"" 1. 連到kibana,並來到home介面 2. 點選右上角三條線,選擇management中的stack management 3. 選擇kibana下的index patterns 4. 點選右上角的create index pattern ## 使用Discover找到資料 1. 使用前須先創建index pattern,接著來到來到home介面 2. 點選右上角三條線,接著選擇Analyties中的Discover 3. 點選左上角的按鈕選擇想要查看的index pattern 4. 可以從下方的Available field選擇想查看的欄位 5. 可以從上方的Search 欄位 ## 使用filter過濾資料 1. 使用前須先創建index pattern,接著來到來到home介面 2. 點選右上角三條線,接著選擇Analyties中的Discover 3. 點選左上角的按鈕選擇想要查看的index pattern 4. 點選左上角的add filter,選擇想要的過濾的field (column) 跟Operator (代表filter的邏輯),接著按Save儲存即可 ## 建立Visualize物件 Visualize物件是將資料可視化後的結果,建立後可以被拉到Dashboard中 1. 使用前須先創建index pattern,接著來到來到home介面 2. 點選右上角三條線,接著選擇Analyties中的Visualize Library 3. 點選右上角的create visualization按鈕,接著選擇Len卡片 4. 選擇index pattern,接著將想要的欄位拖拉到中間 5. 點選右上角的save 命名加儲存,即可成功建立Visualize物件 ## 建立Dashboard 1. 使用前須先創建Visualize物件,接著來到來到home介面 2. 點選右上角三條線,接著選擇Analyties中的Dashboard,再點擊右上角的Create dashboard 3. 點選剛剛建立好的Add from library匯入之前建立好的Visualize物件 4. 可以在這個Dashboard 介面調整圖表顯示區大小和位置 5. 點選右上角的save 命名加儲存,即可成功建立Dashboard # Redis HA 參考資料: https://skychang.github.io/2017/04/09/Redis-Create_Redis_HA/ * HA 指的是High availability,也就是高可用性 * 要保持HA, 至少需要三台server扮演不同的工作 * Master: 負責處理主要的資料讀寫等事務(可讀、寫) * Slave(replica): 負責備份資料,會定期從Master同步資料(只可讀) * Sentinel(哨兵): 負責決定目前哪台server還活著並可以提供服務,若Master無回應時,會將Slave切換成Master;Master重啟後,會將原本的 Master 改為 Slave 可以透過修改redis的config來設定Master,Slave及Sentinel ## Master 設定: 在原本的 bind 127.0.0.1 後面加上這台的私有 ip 加上密碼 ## Slave 設定: 找到 # slaveof 這行,拿掉註解,修改成 slaveof 10.0.0.4 6379 ( 代表 Master 的 ip 位置和 Port ) 找到# masterauth,改成 masterauth foobared ( 代表 Master 的密碼 ) Bind 127.0.0.1 也要改成 Bind 127.0.0.1 10.0.0.5 把 requirepass foobared 取消註解, 讓 Slave 也可以透過 10.0.0.5 並且搭配密碼 foobred 來連入 ## Sentinel 設定: ```conf port 26379 bind 127.0.0.1 10.0.0.6 sentinel monitor master1 10.0.0.4 6379 1 sentinel down-after-milliseconds master1 5000 sentinel failover-timeout master1 900000 sentinel parallel-syncs master1 2 sentinel auth-pass master1 foobared ``` port : 代表 Sentinel Port 的位置,通常大家都會用 26379 bind : 這很重要,至少要加上 127.0.0.1,不然會因為通訊的原因,而造成不斷的去嘗試切換 Master 和 Slave,如果有三台 Sentinel ,後面也要加上這台的 IP,真的不想加這行,也可以把安全模式關閉,改成 protected-mode no sentinel monitor : 要監控的 Master ip 和 port,其中 master1 是自訂名稱。 down-after-milliseconds : 如果在幾毫秒內, 没有回應 sentinel , 那麼 Sentinel 就會將這台標記為下線 SDOWN ( subjectively down ) failover-timeout : 轉移過程多久沒完成,算失敗。 parallel-syncs: 執行完failover任務任務後,其他的slave必須要從新的master中同步數據,這個參數是用來設定同一時間內允許參與同步的最大slave數量;這個數字越大,代表同步完成的時間越短,但會消耗更多系統資源 還要回到 Master 的機器,設定 redis.windows-service.conf, 找到 # masterauth,改成 masterauth foobared ( 代表 Master 的密碼 )。 需要這樣的原因,是因為,到時候 Master 會被切換成 Slave,如果沒有設定 masterauth foobared, 到時候這台機器會無法和未來的 Master 進行連線。 ( 設定完也不忘記重新啟動 ) ## Slave 詳細機制 * redis 預設使用asynchronous(異步) replication,優點是低延遲跟高性能,缺點是可能會有資料丟失的問題 * 建議在Master及Slave中開啟關於persistence的設定;若真的要關閉,應該要避免伺服器重啟後直接重啟redis系統,否則可能會因為系統重啟速度過快,導致Sentinel來不及偵測,而重啟後又將清空後的資料同步至其他Slave中,導致資料丟失 * Redis 有key過期的機制,但在Slave中,不會自動地讓key過期並刪除,而是收到來自Master的Del通知後才會將過期的key刪除;但在使用者讀取的時候,則會根據自己的時間來判斷這個Key是否過期,並回傳對應的結果給使用者 ## Sentinel詳細機制 * failiver是指一個master節點無回應後,自動將它的任務轉移到一個可用的slave節點上的過程 * quorum是指需要多少個Sentinel同意某個master節點無回應,才會將其標記為失敗(ODOWN),並根據ODOWN狀態啟動failiver任務 ex: 有五個Sentinel,quorum=2,當至少有兩個Sentinel覺得Master無回應後,且負責執行failiver任務的Sentinel獲得至少三個其他Sentinel授權,便會執行failiver任務 * Configuration propagation: 當Sentinel成功對Master進行failiver,將開始將新配置發給其他相同Master的所有Sentinel * 因為redis屬於非同步寫入,故在連線中斷時,可能會導致使用者確定寫入的資料遺失,可以加入以下參數來解決,當master發現自己沒辦法將寫入的資料至少轉移至指定數量的slave時,會中止使用者繼續寫入 ``` min-replicas-to-write 1 min-replicas-max-lag 10 ``` * Sentinel可以透過__sentinel__:hello 這個chennel找到Master與其相同的其他Sentinel;也可以透過查詢Redis來找到Master的Slave們。Master新增Sentinels後,會自動更新所有Sentinel的list;由於Sentinel永遠不會忘記其他Sentinel,若確定要移除某個Sentinel,必須先確定移除該Sentinel後,一一reset其他 Sentinel;由於Sentinel永遠不會忘記其他Master,所以想要刪除Master,也必須使用類似的操作 * 可以設定Slave的優先級,當Master故障時優先級較高的Slave會優先成為新的Master。當Master無回應時選擇新的Master邏輯如下: 1. 忽略過早就與Master斷開的Slaves 2. 選擇優先級較高的Slaves 3. 若優先級相同 , 檢查replication offset,並選擇從Master中接收到更多資料的Slaves 4. 若 replication offset還是相同,則按照字典序挑出一個ID最小的Slaves(字典序最小的不一定比較好,這麼做是為了讓整個過程是可被確定的) * SDOWN 與 ODOWN: * SDOWN: 客觀下線狀態,當Sentinel檢查某個Master是否有正常運作時,沒有在指定的秒數內收到來自Master的有效回復時,便會進入SDOWN狀態;處於SDOWN狀態下的slave永遠不會成為新Master的候選對象。 * ODOWN: 主觀下線狀態,當指定數量的 Sentinel都回報SDOWN時,便會升級成ODOWN;若稍後條件不滿足,則ODOWN狀態會被清除;只有在ODOWN狀態下才可能進行failiver任務。 * TILT mode: 一般來說,Sentinel會每秒確認當前時間十次,並紀綠上次確認時間是甚麼時候;若某個瞬間確認的時間跟上次確認的時間相差過大或是負數,就會進入TILT mode: Sentinel仍然持續監測,但其檢測故障的能力已不再被信任,針對任何請求都會做出否定的回應;若30秒內一切正常,則退出TILT mode # Redis persistance redis 是將資料存在memory中的,若電腦關機資料將遺失;為了能永久保存資料,可以使用Redis persistance * 可能的選項為: 1. 無持久性 2. RDB: 儲存資料庫某個時間點的快照,並寫進硬碟裡 3. AOF: 儲存對資料庫下的所有write指令,並在電腦重新啟動時重播這些指令 4. RDB+AOF(重啟redis後,會優先使用AOF) (redis 重啟後,若發現有Redis persistance相關的檔案,將會自動使用) ## RDB 原理: RDB fork(), main process 繼續處理資料庫query,child process 將資料寫進暫時的RDB檔案;完成後,新的檔案將取代舊的(可以在取代前將檔案複製到其他地方以儲存舊版本) 備份方式: RDB file 在產生後便不會再發生任何修改,故可以直接將該檔案複製到任意想要儲存的地方(ex: 雲端) ### 優點 * 非常適合備份,以還原某一時間點的資料 * RDB是一份檔案,可以自由的傳到遠端的數據中心儲存 * 能更快重啟 ### 缺點 * 在上一次備份過後才加入的資料會丟失 * 雖然RDB的備份操作是使用fork()進行的,但資料量過大時可能會消耗系統性能 ## AOF (Append-only file) 原理: 記錄使用者的每條write指令,並包含重寫機制: 只紀錄key值的最終結果,以盡可能地減少AOF的檔案大小;此機制不會中斷本來的query 服務,且不會有資料丟失的問題 (補充機制) 若AOF寫入到一半的時候因為意外而被強制中斷了,預設的情況下系統重啟時會忽略這條被中斷,寫入不完整的指令 若AOF檔案損壞了,預設的情況下會直接出錯;解決方法為使用fix的方法修復 備份方式: 直接複製AOF檔案所在的資料夾即可;注意: 不要在AOF進行rewrite時複製,否則可能會複製到無效的檔案 ### 優點 * AOF只紀錄指令,且可以設定將指令同步至硬碟上的方式,如此一來資料丟失的可能性較小;同步方式可以為 * 不同步,交給作業系統來執行 * 每秒同步一次 * 每次操作時同步 ### 缺點 * AOF檔案通常較RDB大 # Redis cluster * 目的是進行水平拓展,將資料平均分散到不同伺服器上儲存 * 跟redis HA相容,可同時使用 * 原理是在cluster進行寫入操作後,透過一個hash table將資料映射至同一cluster的不同node(節點,代表伺服器)上儲存;hash table 中總共有16384個槽位,可以自由分配給node,當資料流進某槽位時,便會根據分配的設定決定要存到哪個node上 * 每個節點的資訊是互通的;會透過heart beat機制定期追蹤有哪些節點加入及離開這個cluster;為了實現這樣的通訊,每個node都需要開兩個port,一個用來與客戶端通訊(ex: 6379),一個用來實現node之間的通訊(ex: 16379) * 想要啟用redis cluster這個功能,會需要兩個設定檔;除了本來就有的redis設定檔(redis.conf)外,還多了專門用於redis cluster的設定檔(nodes.conf) * 整體啟動流程為: 啟動cluster -> 加入node(cluster 中的node間會自動進行通訊) ->(建立Master跟Slave關係) ->分配hash table的槽位(只有Master node可以被分到) -> 寫入資料(加上 -c 參數以重新導向到hash table所分配的node中寫入) * redis cluster沒辦法保證回應OK給客戶端的write能成功被儲存,例如寫入後、將data傳給Slave前,伺服器因故當機重啟 # redis 底層原理 參考資料: https://xie.infoq.cn/article/78215ce2bb651e7079ea3c80b * string 使用SDS實作 * list 使用linked list實作 * hash 使用hashtable實作 * set 使用hashtable實作 * sorted set 使用skiplist實作 * 若以上資料結構需要儲存的資料量夠小,則會改成intset或ziplist來儲存資料 ## SDS(simple dynamic string) 為了節省空間,SDS像C++ vector一樣,用來儲存字串的空間是動態分配的;當空間不夠時,會自動配置一個兩倍的空間,但已配出的空間不會收回去 ## linked list 底層是double linked list,並使用struct list 來管理這個結構(儲存linked list head, tail, size等資訊),節點內可以儲存任意的值 ## hashtable 實作方法為: 當key發生碰撞時,會使用linked list的方式掛在某個key的後面;當達成一定條件後執行rehash rehash條件: 已保存的value數量/目前的key數量 >5 rehash時,會配置一個兩倍大小的空間,並慢慢地將key-value轉移到新的hash table(具體作法是進行一次對redis的operation時,順便轉移一組key-value至新的hash table中,直到全部都順利轉移後,將新的hash table完全取代舊的,並預先配置一個新的hash table準備下一次的rehash) ## skiplist ## intset 當一個set只有包含整數,且數量不多時,會改用intset的資料結構儲存。 在儲存這些數字時,還會根據這些數字的大小,為每個數字配置適當的記憶體空間以儲存,目的是為了節省空間;若新加入了一個更大的數字,則會擴展每一個數字所分配的記憶體空間大小(因為數字量不大,顧時間成本不會很高) ## ziplist # Nexflix workflow 目的是管理一系列任務的執行順序,並隨時監測執行的狀態 ## 基本概念 * Task: * 最基本的工作單元,可以是微服務等 * 可以由任意的不同語言實作 * 分成三種類型: * simple task: 執行簡單的任務,例如單純的操作資料庫等等 * System task: 一些基本的系統指令,例如sleep等 * operator: 自己定義的複雜任務,例如微服務等等 * workflow: * 由一系列的task組成,定義了如何呼叫這些task的順序跟處理邏輯 * 使用json定義,但可以被可視化成流程圖 * 方框: task * 菱形框: operator * 可以使用常見的邏輯(operator)管理tesk的執行順序,例如迴圈、條件判斷等 ## 執行流程 定義並創建task -> implement task worker -> 定義並創建Workflow -> run Workflow(提供輸入) -> 查看結果 ## 定義並創建task 使用以下json檔案來定義一個task ```json! { "name": "hello_world_test", //定義task的名稱 "description": "a simple task", //關於這個task的說明 "inputKeys": [ //task的輸入 "name" ], "outputKeys": [ //task的輸出 "message" ], "retryCOunt": 3, //當微服務無法正常啟動的時候,嘗試重啟的次數 "timeoutSeconds": 3600, //當超過這個時間後,這個task將被視為TIMED_OUT "timeoutPolicy": "TIME_OUT_WF", //處理timeout的方式 "retryLogic": "FIXED", //重試的方法 "retryDelaySeconds": 60, //多久後再重試一次 "responseTimeoutSeconds": 600, //當任務經過設定的時間後還沒有更新狀態,便會重新安排這個任務(預設3600) "responseLimitPerFrequency": 0, "rateLimitFrequencyInSeconds": 1, //跟分配任務的頻率有關 "backoffScaleFactor": 1, //重新安排任務時的間隔延長係數,第一次重新安排時延遲1分鐘,第二次加1變成兩分鐘,以此類推 "ownerEmail": "foo@bar.com" } ``` 除此之外,還有一些範例中沒提到的參數: * pollTimeoutSeconds: 當超過這個時間後這個任務還沒有被執行,便會標記成TIMED_OUT狀態 * inputTemplate: 預設的input * concurrentExecLimit: 最大可同時執行的task數量 ## implement task worker ## 定義並創建Workflow ```json! { "name": "first_sample_workflow", //workflow 的名字 "description": "First Sample Workflow", //針對這個workflow的描述(可選) "version": 1, //這個worlflow的版本 "tasks": [ //定義這個workflow會用到的task configurations,可以被視為這個workflow的藍圖,非常重要 { "name": "get_population_data", //task的名字,必須要在啟動workflow之前先註冊好 "taskReferenceName": "get_population_data",//在此workflow執行過程中task所使用的暫時名稱,可能有許多暫時名稱指向都代表同一個task(也就是name一樣但taskReferenceName不一樣) "inputParameters": { //這個task所需的輸入參數 "http_request": { "uri": "https://datausa.io/api/data?drilldowns=Nation&measures=Population", "method": "GET" } }, "type": "HTTP" //這個task的類型,SIMPLE代表將會被remote use執行,否則應為其中一種可執行的system type } ], "inputParameters": [], //這個workflow的輸入,通常會將這個workflow中所有的task所需用到的參數都先傳進這裡,接著task configuration中的inputParameters在使用json path 的方式取得自己所需的參數(ex: "movieId": "${workflow.input.movieId}",);或是從其他task中的output取得(ex: "lang": "${loc_task.output.languages[0]}",) "outputParameters": { //這個workflow的輸出 "data": "${get_population_data.output.response.body.data}", "source": "${get_population_data.output.response.body.source}" }, "schemaVersion": 2, //通常固定為2 "restartable": true, //是否允許workflow 重啟 "workflowStatusListenerEnabled": false, //是否允許workflow狀態監聽器運作 "ownerEmail": "example@email.com", //擁有這個workflow的人的email "timeoutPolicy": "ALERT_ONLY", //timeout後如何處理 "timeoutSeconds": 0 //設定timeout的秒數,若這個workflow超過這個時間後還沒有結束則會被標記成timeout狀態,0代表沒有timeout限制 } ``` * 除此之外,還有一些範例中沒提到的可選設定 * inputTemplate: 預設的輸入 * failureWorkflow: 當執行某個task失敗時,可以指定一個failureWorkflow來處理這個失敗任務 * task configuration中: * description: 描述這個task(可選) * optional: 若task執行失敗,此workflow是否繼續(預設為true) * inputExpression: 直接定義這個task所需輸入的來源路徑,跟inputParameters擇一使用 * asyncComplete: 若為false(預設)代表執行到需要外部資源的task時,一執行便將執行狀態設定成"COMPLETED";若為true,則會先設成IN_PROGRESS ,並等待到外部資源完成這個任務 * startDelay: 執行這個任務前的延遲等待時間 ## 實戰演練 1. 在conductor UI那邊定義好workflow及task 2. 使用postman trigger workflow(workflow中包含一至多個task) 3. 本地run一支程式負責接可以執行的task ![](https://hackmd.io/_uploads/r1FLCwp5n.png)