2019年6月28日 星期五

istio 的安裝設定調整 (1)

關於 istio 的安裝設定,先從外圍的 prometheus 和 grafana 講起,這次安裝的版本是 istio 1.2.0

istio 官方文件的安裝方式裡,預設會安裝一份 prometheus ,也可以調整選項把 grafana 的安裝給 enable。

因為已經安裝了 prometheus-operator ,希望能夠整合到一起。
比較簡單的方式,就是讓 istio 依照預設也安裝一份 prometheus ,然後從 prometheus-operator 的 granfana 新增一個 data source 即可。
不過最好的情況,當然還是維持只有一個 prometheus 是比較好的做法。

這邊有一篇文章說明了如何用 prometheus-oprator 來收集 isito 的資訊。
我是設定好後才看到上面那篇文章,做法上大致相同,只是我用的方式比較刻苦一點。

先取得 istio 裡的 prometheus 的 config ,最簡單的方法是先依預設方式安裝好一份 istio ,然後從裝好的 prometheus 裡取出設定檔,或者依照上面那篇文章提供的設定內容。

原本以為直接修改 prometheus  secrets 裡的設定就好,發現 secrets 裡的設定檔是壓縮過的格式 prometheus.yaml.gz 。不過想說這也不是什麼大問題,取出來解壓縮,修改完在壓縮後更新回去就好。
kubectl -n monitoring get secrets prometheus-prometheus-prometheus-oper-prometheus -o jsonpath='{.data.prometheus\.yaml\.gz}' | base64 -d | gunzip > /tmp/prometheus.yaml

然後把前面取出的設定整合到 /tmp/prometheus.yaml ,然後 gzip、base64 最後 kubectl edit 貼回 secret 就搞定。

咦?不 work 。仔細一看,kubectl edit 修改完儲存後,再進去又變回原本的內容。

仔細查看 prometheus-operator 的機制,是要透過另外的 secrets 來新增 scrape config
最簡單的方式,就是在 prometheus-operator values.yaml 的 additionalScrapeConfigs 加上自訂的 scrape config

如果之前沒有設定過 additionalScrapeConfigs ,那在這次設定完之後,會發現安裝的 namespace 下多了一個 secrets prometheus-prometheus-oper-prometheus-scrape-confg,新增的設定就在這個 secret 裡
kubectl -n monitoring get secrets prometheus-prometheus-oper-prometheus-scrape-confg -o jsonpath='{.data.additional-scrape-configs\.yaml}' | base64 -d


這個沒有 gzip 壓縮過了,之後如果要再新增 scrape config,可以直接修改這個 secret,就可以不用再透過 values.yaml 了。雖然透過 helm 來更新還是比較方便的,不過經常是因為在測試設定的方式來做更新,一直透過 helm 會讓安裝版本記錄更動太多。比較好的方式是測試設定正確後,再修改 values.yaml 做更新,這樣可以確保 helm history 裡的安裝歷史紀錄的版本都是可以運作的。

把 prometheus 搞定後,剩下的 grafana 就比較簡單了。

我是直接把 istio 目錄下的 charts/grafana/dashboards/ 裡的所有 json file ,一個一個從 grafana UI 裡做 import 。如果有些 dashboard 沒資料,可能是因為 promethues scape config 沒設定好的關係,不過至少 gallary pilot mixer 三個 dashboard 應該是有資料的。

簡單的部分先到這邊了。

2019年6月20日 星期四

讓 prometheus-operator 監控 cluster 外部的 metrics

故事的緣起是在 enable 公司的 ceph dashboard plugin 時,發現 ceph 還有一個 prometheus 的 plugin ,這個 plugin 是一個 prometheus exporter ,也就是 enable 這個 plugin 後,就可以從
http://[ceph ip]:9283/metrics

取得 ceph 的 metrics data。那要如何設定讓 kubernetes 內的 prometheus 可以抓到 ceph 的 metrics ?故事就是這樣開始的...

我是用 prometheus-operator 的方式安裝的,這篇對 prometheus-operator 的架構有很不錯的介紹。從這邊可以知道,要新增 metrics 的關鍵在於 servicemonitor 這個 resource ,這是安裝 prometheus-operator 新增的 CRD resource 。

所以新增一個 servicemonitor 去設定要拉 metrics 的來源 service 就好了,簡單吧?
事情好像沒想像中那麼美好,ceph 是在 kubernetes 外部,cluster 內部沒有 ceph 相關的 svc 設定,這段要自己搞定。

凡事問 google ,找到這篇,節省了非常多的時間。

建立 ep => 建立 svc => 建立 servicemonitor 
打完收工。

喔,不,文章中有一些小地方需要修正一下。
最主要的關鍵是文章裡 "BOOM" 那行找到的 label ,和現在 prometheus-operator 的設定不一樣,這應該是版本的關係。

修正 label 的部分,以及一些必要的修改 (像 ip 之類的),剩下的還有一些小錯誤要修正,調整完後就差不多真的完工了。

kubectl apply 上面設定好的 yaml 後,就可以從 prometheus 的 UI 來驗證一下
* 從 Status 的 target 和 service discovery 可以看到新增的 servicemonitor 名稱
* 從資料篩選的地方輸入 ceph 就會自動跳出很多符合的名稱可以篩選,例如 ceph_bluefs_bytes_written_slow

prometheus 這邊搞定後,就往 grafana 前進,隨便找個 for ceph 的 dashboard ,例如編號 7056,從 grafana 匯入這個 dashboard ,就可以從 grafana 看到 ceph 的資料了。

就數值的部分和 ceph 本身的 dashboard 看起來都差不多,不過有一些小地方可能有點問題。畢竟是用別人現成的 dashboard,這個微調的部分就暫時不管了。

這樣對 prometheus-operator 的瞭解又多了一點,之後要拉其他的 application 的 metrics 就比較有把握了。

Log collect by Loki

比較常看到的 log collect 會使用 ELK 或 EFK 的架構,因為之前有安裝過 ELK 覺得太笨重,所以想試看看 Loki ,這樣 metrics (Prometheus) 和 logging (Loki) 就可以共用一套前端 (Grafana) 來處理。

Loki 基本上非常好安裝,用 helm 安裝的話甚至連 values.yaml 都不用修改。
helm repo add loki https://grafana.github.io/loki/charts
helm repo update
helm install -n loki --namespace logging loki/loki-stack

然後因為我原本就已經有安裝 prometheus 和 grafana ,所以直接到 grafana 的 UI 去設定。新增一個 data source ,選擇 Loki。在設定 loki url 的地方,如果 loki 安裝的 namespace 和 grafana 一樣,就可以直接照官方設定為 http://loki:3100/

因為我是設定在不同的 namespace,所以要修改為
http://loki.logging:3100

這個只要略懂 kubernetes 內部 DNS 運作方式的話,應該很好理解和判斷。
設定好之後,到 grafana 選單的 Explore 選項,就可以看到 log data 慢慢出現了。

loki 是用 promtail 來收集 log ,所以會看到 promtail 這個 DaemonSet 在每一個 node 上。而 promtail 預設是收集 container 的 log  ,也就是 /var/lib/docker/contaibners 及 /var/log/pods 裡的 log。 promtail 的設定可以參考 helm chart 裡的 charts/promtail/values.yaml 這個檔案。

如果要將前一篇的 audit log (或者連 host 的 syslog 一起) 也放進 loki 的話,理論上應該是改 promtail 設定是最適合的。 不過對 helm chart 不熟,對 promtail 設定的語法也不熟 (語法和 prometheus 一樣) ,而且也想嘗試用其他方式餵資料給 loki 看看,所以選擇用 fluentd 來做。

我是直接找一個現成的專案來試,這個專案把 loki plugin for fluentd 包進去,只要改幾個地方就可以運作了。

先修改 values.yaml,設定 loki 的 url ,因為我預定安裝在和 loki 一樣的 namesapce ,所以只要設定
http://loki:3100

還有設定 source 的 log path ,例如
path /var/log/kubernetes/kube-audit

另外要修改 templates/05-deployment.yaml 這個檔案,裡面有一個 nodeSelector 設定,目的是篩選 master node 來 deploy (因為只有 master node 上會有 audit log)。
但是預設的 nodeSelector 的篩選條件 (role: master) 和我現有的環境不符合,我也不想為了這個去設定 node 的 label ,所以修改這邊的設定為
node-role.kubernetes.io/master:
另外因為我這邊是多台 master node ,如果以 deployment 方式部署較不適合,所以把 Deployment 改為 DaemonSet ,在這份 yaml 中,直接這樣修改是沒問題的。
都修改完之後,就可以 deploy 了。
helm install --name=fluentd-loki ./ --namespace=logging

然後就可以到 grafana 的 UI 去做驗證了。
選擇 label 為 {env="dev"} 就可以看到透過 fluentd 取得的 audit log 了。
那這個 label 是哪來的? 

回頭看 values.yaml 的設定裡,有一個 extra_labels ,就是設定透過 fluentd 傳進去的 log 會有什麼 label 。

接著可以參考 grafana 官網,看看怎樣篩選需要的 log ,例如要找出 audit log 裡,verb 是 create 類型的,可以這樣篩選
{env="dev"} verb="create"


到此先告一段落囉。

2019年6月19日 星期三

kubernetes 內建的 audit 機制

官方資料裡有關於 audit 尚稱完整的說明,不過在實際上與設定上還是有一些需要摸索的地方,在這邊做個紀錄。

audit 的功能,是透過 kubernetes API Server 來做,所以首先是要修改 apiserver 的設定。如果是用 kubeadm 安裝,設定檔是在 /etc/kubernetes/manifests/kube-apiserver.yaml

不過在修改設定檔之前,要先設定好 audit 相關的 policy file 提供給 apiserver ,因為 apiserver 是屬於 static pod ,一修改設定就會直接重啟 pod。

Policy 設定的方式稍後再說,先用官方提供的最簡範例
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata

為了方便測試 (稍後說明),將上面內容存放在 /etc/kubernetes/pki/audit-policy.yaml ,然後就可以開始修改 apiserver 的設定檔了。

修改的內容最主要是開啟 audit 及相關參數,這邊先只設定必要的參數,分別是 policy file 的路徑、audit log 的路徑、以及 log 的格式
    - --audit-policy-file=/etc/kubernetes/pki/audit-policy.yaml
    - --audit-log-path=/var/log/kube-audit
    - --audit-log-format=json

前面提到 policy file 放置的路徑是為了方便,原因在於 apiserver 是一個 pod , pods 裡是看不到 host 的檔案的,而觀察一下 apiserver 的 yaml 設定,可以發現 /etc/kubernetes/pki 這個路徑是有掛載在 pod 裡,所以為了方便,把 policy file 放在這邊就不需要額外設定,就可以讓 apiserver 取得。如前所述,修改完設定檔後,apiserver pod 會自動重啟。

接下來用 kubectl exec 的方式進到 apiserver pod ,確認一下 pod 裡的 /var/log/kube-audit 是否有產生。以上面設定的 policy 內容來說,會把所有事件的 metadata 都記錄下來,所以如果有設定正確的話,應該會看到 audit log 如雪片般的飛來。

不過把 log file 放在 pod 裡,這樣不容易管理與稽核,這時有四種做法。

第一種是把 audit log 改送到 stdout (修改 log path 的設定變成 --audit-log-path=-),這樣就會和其他的 container 一樣,會把 log 寫在 host 的 /var/lib/docker/containers/ 裡。不過這種做法,會因為 apiserver 的 log format 是 json ,要和其他 container log 一起管理會產生差異,所以我放棄這種方式。

第二種方式是透過 sidecar。有些 app/image 沒有把 log 丟到 stdout/stderr ,就要透過 sidecar 的方式去收集 log ,常見的做法是把 fluentd 作為 sidecar 來搜集 log 。不過因為 apiserver 是 static pod,我也不太傾向用 sidecar 的方式來處理。

第三種是把 audit log 設定為 webhook ,把 log 送到指定的 url 。因為 apiserver static pod ,這種方式需要在 cluster 外部設定好可以收 log 的 webhook ,如 logstash 或 fluentd 等。因為我目標是把 log 整合在 cluster  內部的 app ,所以也沒採取這種方式。

最後一種方式就是直接把 host 上的路徑掛載到 apiserver 上產生 log 的路徑下。
例如我規劃把 audit log 會寫到 host 的 /var/log/kubernetes/ 下,就修改 apiserver 設定檔,增加內容如下
# 指定 host 路徑
  - hostPath:
      path: /var/log/kubernetes
      type: DirectoryOrCreate
    name: k8s-audit
# 指定掛載路徑
    - mountPath: /var/log
      name: k8s-audit
設定正確的話,就可以看到 host 的 /var/log/kuberentes/kube-audit 裡的 audit log 如雪片般的飛來。

在思考如何把 audit log 和其他 log 一起管理之前,先來調整一下 policy file ,否則 log file 成長的會非常快。

官網上對於 policy file 有一些基本的介紹,不過這篇寫得比較完整

設定檔裡面的幾個主要的項目:

* omitStages
  指 audit log 發出來的時機
  選項有 RequestReceived / ResponseStarted / ResponseComplete / Panic

* level
  記錄 log 內容的完整程度,None 就是不紀錄
  選項有 None / Metadata / Request / RequestResponse

然後參考 rules 裡的其他設定項,如 verbs、users、resources 等,仔細看會發現和 kuberntes RBAC 的設定方式一樣,例如 verbs 的選項有
["get", "list", "watch", "create", "update", "patch", "delete"]


所以假設今天要設定的規則是,只記錄 namespace 的 create 和 delete,其他通通不記錄,那 rules 的寫法會如下
apiVersion: audit.k8s.io/v1beta1
kind: Policy
omitStages:
  - "RequestReceived"
rules:
  - level: Request
    verbs: ["create", "delete"]
    resources:
    - group: ""
      resources: ["namespaces"]
  - level: None

到這邊可能遇到兩個問題

* 修改 policy file 並不會讓 apiserver pod 重啟,如果用 kubectl delete pod 的方式對 static pod 也是無效的,所以最簡單的方式,就是把 apiserver 的設定檔移到別的路徑下再移回來,就可以讓 apiserver pod 重啟了。

* 如果 master node 不止一台,而且有用 haproxy 之類的擋在前面做 load balance ,就會遇到和我一樣的問題。因為對 api server 的 request 是會輪詢的,所以要測試時會出現非預期的結果。我一開始沒注意到這個問題,想說一樣的 kubectl create ns 指令,為什麼有時候有產生 audit log ,有時候沒有。後來修改 kubectl context 直接指向有變更設定的 apiserver ,就可以很正常的依測試產生 audit log 了。


audit 的部份到這邊告一段落,接下來把 host 上面的 audit log 和其他 log 整合一起,就是另一個故事了。


2019年6月13日 星期四

alertmanager 初探

環境是延續之前安裝的 prometheus-operator

alertmanager 裡的設定主要分為幾個項目:

* receivers : 設定有哪些可以接收 alert 的接收端
* routes: 設定哪些 rules 要送到哪些 receiver
* templates: alert 的內容可以套用預先定義好的 template

prometheus-operator 裡預設的 receivers 是 null ,也就是完全不會發通知出去,這設定可以從 alertmanager 的 UI 的 Status 看到。

如果要從 command line 看 alertmanager 的設定,可以用以下指令
kubectl -n monitoring get secrets alertmanager-prometheus-prometheus-oper-alertmanager -o jsonpath='{.data.alertmanager\.yaml}' | base64 -d



首先替 alertmanager 加上 receiver 的設定,一樣還是修改 values.yaml 再用 helm upgrade 更新會比較方便。找到 values.yaml 關於 alertmanaget 的 config 的地方,修改成如下 (以通知到 slack channel 為例) :
config:
  global:
    slack_api_url: SLACK_HOOK_URL  // 置換成自己設定好的 slack hook url
  route:
    group_by: ['alertname', 'datacenter', 'app', 'job']
    receiver: 'slack-notifications'
  receivers:
    - name: 'slack-notifications'
      slack_configs:
      - channel: '#k8s-alertmanager'
         text: '{{ template "slack.k8sbridge.txt" . }}'

最後那行是說 text 要套用指定的 template,所以同樣在 values.yaml 設定 template 的部分
templates:
  - '*.tmpl'
這兩行是告訴 alertmanager 要 include 預設路徑下的所有 .tmpl 檔案
然後把後面 templatesFiles 那一大段都 uncomment,只修改 define 的 tempalte name,要對應前面設定的 text 裡的 template name,例如:
  templateFiles:
    template_1.tmpl: |-
        {{ define "cluster" }}{{ .ExternalURL | reReplaceAll ".*alertmanager\\.(.*)" "$1" }}{{ end }}
        {{ define "slack.k8sbridge.tet" }}

設定完以後,用 helm upgrade ,沒問題的話就會看到指定的 slack channel 裡出現 alertmanager 送出來的訊息。


2019-09-21 補充:
helm 安裝的 prometheus-operator 的 values.yaml 裡的 templateFiles 裡的範例,少了兩個最後的 {{ end }}
這個 template 語法和 go-template 是一樣的,所以 {{ define "xxxx" }} 和 {{ range }} 後面都要配對一個 {{ end }}

之前設定時,正好去忙別的事,沒有 check log 驗證設定是否正確。這兩天看到 alertmanager log 裡有錯誤訊息,抽時間來修正一下。

2019年6月11日 星期二

helm 的零碎小知識

記錄一下 helm 常使用的指令及一些小細節

安裝 helm 時,會先用 helm init 建立和 kubernetes cluster 的關係,也就是會 deploy 一個叫 tiller 的 deployment 到 kubernetes (預設在 namespace kube-system) 。

後續所有套件安裝的資訊以及將套件 deploy 到 kubernetes 都是透過 tiller 進行,因此 tiller 需要足夠的權限,而 tiller 的權限則是在 helm init 決定]。所以在 helm init 前,需要在 namespace kube-system 先建立好一個 service account,並且給予足夠的權限。然後在 helm init 時,用 --service-account 指定建立好的 service account。

helm 基本指令的使用都還蠻直覺的,不過在看指令前,先簡單了解一下 helm 的套件運作方式。

在 kubernetes 裡,部署、設定、管理之類的動作都是透過 yaml 進行,而一個軟體的部署可能會用到很多個 yaml 檔來設定,而且其中可能很多設定內容都是重複的。helm 做的事情就是把要讓使用者設定的項目提取出來,放在 values.yaml 裡,然後在安裝時,把 values.yaml 裡的設定內容轉化成真正的 yaml 檔案後,再部署到 kubernetes 裡。

所以用 helm 安裝套件時,大部分的情況都是會需要變更 values.yaml 的設定後再部署。也就是說大部分的安裝指令會類似如下:
helm install -f values.yaml -n pm --namespace monitoring stable/prometheus

會指定安裝後的名稱、要安裝到哪個 namespace、以及要讀取哪個 yaml 檔裡的設定。

但是一般來說,除非對要安裝的套件很熟了,否則不會知道 values.yaml 裡設定了什麼,所以要先透過 helm 指令,將套件抓下來後,取得裡面的 values.yaml 來修改。
helm fetch stable/prometheus

這樣就可以把 promethues 這個套件下載回來,下載的套件是一個 tarball ,解開來後,裡面就會有一個 values.yaml ,可以修改完後,用上述的指令進行安裝。

然而通常情況是,很多套件一開始使用時,values.yaml 設定的結果還需要調整。
有些小地方,可能直接用 kubectl 指令做一些調整就好,但是有些設定可能需要修改 values.yaml 再跑 upgrade 會方便一些。
helm upgrade -f values.yaml pm stable/prometheus

在刪除套件時,因為我大部分都是測試性質,不太會有 rollback 需求,所以通常都會完整的移除套件。有沒有加 --purge 參數的差別可以看這篇
helm delete pm --purge

另外較常用的指令大約就是
helm ls  #查看已安裝的套件
helm status pm # 查看已安裝套件名稱為 pm 的安裝資訊

最後是關於 private repo for helm
我是用 harbor 當 helm 的 repo,在啟動 harbor 時,加上 --with-chartmuseum 參數,harbor 就可以做為 helm 的 private repo。

要讓 helm 使用 private repo ,要先進行一些設定

* 新增 repo 的 url 到 helm,用 helm repo add 指令
* 安裝 helm plugin ,後續才能用 helm push 把套件推送到 repo 上。

關於這部分,這篇有蠻完整的介紹。
我個人覺得這部分的使用有點麻煩,因為我架設的 harbor 有使用 ssl ,因此要 helm repo add 或 helm push 時,都要指定一堆憑證路徑的參數 (--ca-file --cert-file --key-file)。
雖然這可以透過一些方式來簡化,但要自動化處理時,要把這些加入流程又要注意 security 會有點繁瑣。

2019年6月8日 星期六

CKA 小記

距離當初考 CKA 三個月了,簡單記錄一下考試的經驗。

題目共 24 題,其中 6~9 分的大約 4 題,其他 20 題則是 1~4 分。

在分數比較多的 4 題裡,大致上是如下題型:

1. 提供 ca 等憑證的路徑,建立起 master node ,也就是官網裡的 tls bootstrapping 的部分

2. secrets 的設定使用的情境

3. cluster 內的 DNS 查詢,也就是會先要求建立一個 pod 和 service ,然後用 nslookup 分別作正反查

4. 用 etcdctl 對 etcd 做備份

剩下的 20 題裡,大約有 2 題是要求找出 cluster 運作出問題的地方 (一題是針對 worker node ,另一題是針對 master node)。剩下的大部分都是比較基本的 kubectl 操作,從最簡單的 "找出 cpu 使用最高的 pod",到比較複雜一點的 "deployment 的 rollout history and rollback",不會有太刁鑽的題目。

雖然考試會用到 5~6 個 cluster ,但前 18 題左右都是在第一個 cluster 裡面考基本的題型,然後後面 3 個 cluster 則是建立或除錯 cluster,最後可能有一個是要設定 storage 相關的題目。

也就是說,即使放棄掉比較偏系統維運方面的題目,大致上也還有 80 分上下是可以透過 minikube 之類的環境就可以輕易做練習來準備的範圍。

所以如果單純以準備考試來說,基本操作的熟練度要夠,以及針對已知題型的練習也足夠,那要低空飛過只求及格是不會太難的。當然臨場的反應、細心還有緊張程度都會有些影響,這就只能靠個人去控制了。

如果去網路上查 CKA 如何準備,十篇大概會有七八篇會推薦 kubernetes the hard way ,主要是針對 tls bootstrapping 那一題。

不過我個人是覺得,如果是新手,為了準備這題,所花費準備的時間可能是其他所有題目的好幾倍,而且可能會相當挫折。把 kubernetes the hard way 從頭理解一遍當然是不錯,但是就準備考試本身來說,反而可能會是個不利的因素。除非已經是有一定熟悉度,目標往滿分邁進,否則我會建議掌握基本分和基本題型會輕鬆許多。

如果希望事先了解一下考試的介面,可以參考 arush 提供的測試環境
雖然和實際考試的有所差異,但可以作為參考和練習。

另外當初為了準備考試,有用 asciinema 錄製一些操作記錄,有興趣也可以參考。


就和所有的認證一樣,考過不代表高手,高手也不是一定就能考過。準備是必要的 (對大多數人而言),而考過只是代表一個起步而已。起步早晚不是重點,能一步步走下去才是考驗。



2019年6月6日 星期四

Prometheus-Operator 安裝設定

用 helm 安裝的 prometheus-operator 預設就有 mixin 的相關設定及 grafana dashboard ,會比用 helm 安裝的 prometheus 方便許多 (要自己安裝 grafana 及相關設定,如果要安裝 mixin 還要設定 prometheus rules 及 grafana dashboard 等)

用 helm 安裝的話,好像沒什麼好寫的,呃
helm install stable/prometheus-operator --name prometheus --namespace monitoring

搞定了!
先把 promthues 和 grafana 的 service type 改為 Loadbalancer ,來看看安裝結果。

咦?首先從 prometheus 的 UI -> Status -> Targets 看到 etcd 的 endpoints 狀態是 down
cluster 正常的情況下, etcd 的狀態不可能是 down 的,所以這應該是 prometheus 設定問題。

查了一下資料,這是因為 etcd 預設是需要憑證才能 access (如果是用 kubeadm 安裝的話)。就好像用 etcdctl 的話,要指定 ca crt key 等憑證的參數才能 access etcd 一樣。

從查到的資料來看,文章裡的解決方法,已經不適合目前安裝的版本 (prometheus-operator-5.7.0)。

目前的版本已經考慮到這個情況,在 values.html 已經提供可參考的設定方式。
helm fetch stable/prometheus-operator
tar zxvf prometheus-operator-5.7.0.tgz

然後修改 prometheus-operator/values.yaml 及相關設定,流程大致如下:

1. 尋找 etcd 相關的設定,可以看到有提供相關的設定,在 serviceMonitor 的後面調整為
schema: https
caFile: "/etc/prometheus/secrets/etcd-certs/ca.crt"
certFile: "/etc/prometheus/secrets/etcd-certs/healthcheck-client.crt"
keyFile: "/etc/prometheus/secrets/etcd-certs/healthcheck-client.key"

2. 尋找 secrets 的設定,調整為
secrets:
   - etcd-certs

3. 先還不要更新 helm package ,上面第二個設定的意思是,要把能夠 access etcd  的幾個憑證檔放到 etcd-certs 這個 secrets (這個 secrets 不存在,是要自行建立的)
kubectl -n monitoring create secret generic etcd-certs --from-file=/etc/kubernetes/pki/etcd/healthcheck-client.crt --from-file=/etc/kubernetes/pki/etcd/healthcheck-client.key --from-file=/etc/kubernetes/pki/etcd/ca.crt
   注意,這是要在 master node 上執行的,因為如果是用 kubeadm 安裝的話,這幾個憑證在 master node 才有。如果 etcd 是自行安裝,或是透過其他方式如 kubespray 安裝的話,就要自行調整產生 secrets 的方法了。

4. 可以更新 helm package 了
helm upgrade -f prometheus-operator/values.yaml prometheus stable/prometheus-operator

到這邊算是修改完成了,從 prometheus UI 上已經可以看到 etcd dashboard 裡有資料了。

遇到問題的話,可以檢查幾個地方

1. 檢查 servicemonitors 裡 etcd 相關的設定, servicemonitors 是 operator 建立的 CRD
kubectl -n monitoring edit servicemonitors prometheus-prometheus-oper-kube-etcd
   看裡面的設定是否有包含 values.yaml 裡有關憑證的設定

2. 檢查 pods 路徑下是否有確實把指定的 secrets 掛載起來
#kubectl -n monitoring exec -it prometheus-prometheus-prometheus-oper-prometheus-0
$ ls /etc/prometheus/secrets/etcd-certs/


2019年5月15日 星期三

將 windows server 加入 kubernetes cluster 成為 worker node

k8s 官方文件

ms 官方文件

kubernetes 原本環境的調整

  • CNI 限定 flannel (其實有三種,只是裡面最常見的是 flannel,官網範例也是用 flannel)
    flannel 的設定要照官網上的建議修改 net-conf.json 及 cni-conf.json 的內容
  • 修改 DaemonSet 的 yaml ,包含 kube-proxy、kube-flannel-ds-amd64、metallb 等
    spec.template.spec.nodeSelector 加上 beta.kubernetes.io/os=linux

    既有的 DaemonSet / Deployment / StatefulSet 等等,全都要做類似的修改。
    之後新增的也都要加上 nodeSelector。


windows server 2019 上的設定 (後面的操作全都是透過 PowerShell )

  • 安裝 docker

Install-Module -Name DockerMsftProvider -Repository PSGallery -Force
Install-Package -Name Docker -ProviderName DockerMsftProvider                            Restart-Computer -Force
Start-Service docker

  • 取得 kubernetes 的 certificate file

mkdir c:\k
cd c:\k
scp root@[master ip]:/root/.kube/config c:\k\config

  • 取得 windows node 上需要的 binary file,例如 kubelet、kube-proxy 等
    這邊兩個官網給的說明都有點不太清楚,不知道為什麼不直接提供連結
    找 node binaries 裡的 kubernetes-node-windows-amd64.tar.gz

wget -O c:\k\node.tar.gz https://dl.k8s.io/v1.13.6/kubernetes-node-windows-amd64.tar.gz
tar zxvf node.tar.gz
cd kubernetes\node\bin\
cp kube-proxy.exe c:\k\
cp kubelet.exe c:\k\
cp kubectl.exe c:\k\
      為什麼不一次 cp 三個檔案? 因為 powershell 裡就是沒辦法一次 cp 多個檔案,可能要加參數,不過我懶得查....


  • 這邊有一段是 kubernetes 官網上沒有但是 MS 官網有的環境變數設定

$env:Path += ";C:\k"
[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\k",[EnvironmentVariableTarget]::Machine)
$env:KUBECONFIG="C:\k\config"                                                                     
[Environment]::SetEnvironmentVariable("KUBECONFIG", "C:\k\config", [EnvironmentVariableTarget]::User)

  • 下載 windows 上的 flannel binary file

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
wget https://raw.githubusercontent.com/Microsoft/SDN/master/Kubernetes/flannel/start.ps1 -o c:\k\start.ps1

  • 用 start.ps1 script 啟動 flannel 並帶起 kubelet 及 kube-proxy
  • 如果 flannel 設定都是預設值的話,指令大約如下
.\start.ps1 -ManagementIP -NetworkMode overlay -ClusterCIDR 10.244.0.0/16 -ServiceCIDR 10.96.0.0/12 -KubeDnsServiceIP 10.96.0.10 -LogDir c:\k

剩下是測試驗證了,照 MS 官網的文章,和一般 kubernetes deploy 的方式一樣
不過測試用的 image 感覺下載很慢,可以先用 docker pull 下來後,再跑測試會比較方便一些。
 

2019年5月8日 星期三

zero downtime in kubernetes

這是 原始文章,這篇不是翻譯,算是個人解讀後的摘要,如果有誤請指正~
原文有更詳細的解說,尤其第四篇文章內有關於 PDB 機制圖文並茂的解說。
要注意的是,這篇講的 zero downtime,指的是因某些需求而必須要主動停機的情況,如何在主動停機時不會中斷服務。


* 定義所遇到的挑戰為何
   假設目前有兩台 worker node,上面已經在跑一些服務。如果想要升級 worker node 的 kernel 時應該如何進行?
   一個方式是先裝新的 worker node join cluster,然後把原本 worker node 關機。           

   這會產生哪些考量?
  a.  假如這個 pods 需要在結束前做一些清理動作,但是關機程序沒有時間讓 pods 完成這些動作。
  b.  假如所有原本的 nodes  一起關機,會產生一段時間的服務中斷。

  首先要如何避免讓 pods reschedule 到原本的 pods 上?
  用 kubectl drain 設定原本的 node,確認所有的 pods 都移走後再關機。

  不過用 drain 的方式還是有兩個因素會造成服務中斷:
  a.  pods 裡的 applicaion 要能處理 TERM signal,因為 kubectl drain 是送 TERM signal 來 evicted pods。
       kubernetes 會在送出 TERM signal 後的一定時間內,去強制結束掉還在執行的 container。
  b.  如果 pods 在新的 node 起來前,舊的 node 上的 pods 就先都被終止,會產生服務中斷。

  Kubernetes 提供了三種機制來處理主動中斷 (例如 drain) 的情況
  a. Graceful termination
  b. Lifecycle hooks
  c. PodDisruptionBudgets


* Gracefully shutting down Pods

  kubectl drain 讓 kubelet 去 delete pod 的流程
  a. kubectl drain 發出通知給指定的 node,讓該 node 的 kubelet 開始準備做 delete pods 的動作
  b. kubelet 會執行 pod 定義裡的 preStop 設定
  c. preStop 執行完後,kubelet 會送出 TERM signal 給 pod
  d. kubelet 送出 TERM signal 後,會等待一個時間 (grace period, 預設是 30 秒) 後強制刪除 pod
 
 也就是在 kubelet 強制刪除 pod 前,有兩個時間點可以讓 pod 做清理的動作。
 例如說,可以設定應用程式收到 TERM signal 時停止接受新的工作,並且在完成所有已收到的工作後自行停止。

  如果沒辦法修改應用程式去處理 TERM signal ,可以設定 pod 的 preStop 去終止應用程式。

  不過在後面這種情況下,應用程式雖然可以透過 preStop 停止,但是 service 那邊在可能還不知道的情況下(見下節說明),還會繼續把 request 往 pods 送過去,使用者就會看到錯誤或服務被拒絕的結果。

* Delaying Shutdown to Wait for Pod Deletion Propagation

  回頭來看,當送出 delete pods 時,整個 kubernetes cluster 會進行哪些處理?
  a. kubelet 會開始進行 delete pod 的流程,如同前一節描述的
  b. 所有 node 上 kube-proxy 會移除該 pod ip 相關的 iptables rules
  c. endpoints controller 會移除和該 pod 相關的 endpoint,也就是會從 Service 中移除該 pod
 
  這就是為什麼透過 preStop 終止 container 後,流量 (request) 還可能會繼續往 pods 送的原因。

  一種應對方式是,確保 kube-proxy 和 endpoints controller 都處理完後,再終止 container。
  不過這方式是非常困難的,因為 node 可能有網路延遲,或者 node 數量非常非常多。

  實際上沒有辦法做到 100% 服務不中斷,只能盡可能做到 99%。
  方法是在 preStop 用 sleep 指令延後終止 container 的動作,在 "Kubernetes in Action" 一書中建議 sleep 的時間是 5~10 秒。

  也就說,如果在這 5~10 秒內,endpoints controller 可以移除相關的 endpoint,就可以在 container 終止前停止把流量送過去,也就不會有中斷服務的情況。

  此時 Deployment 會因為要保持 replicas 設定的數量在新的 node 建立新的 pods。         
  接下來的問題是,假如同時 drain 所有原本的 node,可能產生的問題是,在新的 pods 產生前,所有原本的 pods 都終止了,而造成了服務中斷。

  如果不要同時,而是逐步 drain node 會遇到什麼問題?
  第一台終止的 pod ,可能被安排到另一台原本也要停機的 node 上。也就是該 Deployment 的兩個 replicas 都在同一台準備要停機的 node 上。 當輪到這台 node 要 drain 時,就會造成這個 deployment 的 pods 全都準備終止的情況。下一節會來處理這個處理這個問題。


* Avoiding Outages in your Kubernetes Cluster using PodDisruptionBudgets

    使用 PDB (PodDistruptionBudgets) 這個機制來處理上節最後面提到的問題。
    PDB 是用來設定對於終止 pod 行為的容忍機制。

    如果在 PDB 的 yaml 設定 minAvailable: 1,表示至少要有一個 pod 存活。這可以防止所有 pods 全部一起被 delete 。在上述情境中,會等到新的 node 裡至少有一個 pod 起來後,才會讓原本最後一個 node 裡的 pod 被刪除。


綜合以上幾個設定方式,可以達到 zero downtime 的目標。

2019年5月7日 星期二

用 ansible 架 kubernetes cluster

用 ansible 架 kubernetes 不是新鮮事,成熟的專案有 kubespray,而 openshift 也是用 ansible 安裝。

會接觸 ansible 並用來裝 kubernetes cluster ,在於我想要整合用 ansible 去建立 vm (base on kvm),vm 是使用預先建立好的 CentOS 7 的 image。

因為是 vm 環境, kubespray 就顯得太龐大了,因此決定自己來寫一個 kvm 版本的安裝流程,從 virsh 建立 vm 開始,到設定 vm 網路環境,然後建立 kubernetes cluster。

這過程花了大約兩個禮拜的時間完成,只能說 ansible 真是一個很容易陷入的工具,因為太容易上手並取得成就感了。兩個禮拜裡從基本的 playbook 到建立 roles ,不斷地重構原本的程式並加入新的功能,以及嘗試讓設定更有彈性。雖然對 ansible 掌握度還不夠,但已經足夠完成目標想做到的 (基本) 程度了。

透過 inventory 裡的設定,可以建立符合需求數量的 vm ,安裝 haproxy + kubernetes cluster + harbar ,以及 kuberntes 一些基本的環境 (metallb + nginx-ingress + helm + metrics-server),harbar 也整合到 kuberentes cluster 。

對我來說,ansible 最難掌握的就是變數的傳遞,花了最多時間去查資料及適應 ansible 的邏輯。 在不同 playbook 間要傳遞變數,我目前知道的方式是 add_host,透過把變數放到虛構的 host 裡,因為 hostvars 相當於一個 global 的變數。另外就是不同方式設定變數的 priority,沒有弄清楚前也吃了一點苦頭。

因為 vm image 是可以自訂的,所以可以先把一些必要的東西先埋進去,這讓 ansible 在處理上也簡單不少,這算是用 vm 來處理的一個優勢。對於已經有 kvm 環境的人來說,只要硬體資源足夠,預先件好需要的 image 後,用 ansible 搭一個上述的 kuberentes 環境,大約只需要 3~5 分鐘左右的時間。

ansible 之旅暫時先告一段落,先回頭看 kubernetes 相關的工具與管理,等到對於 kubernetes 維護管理層面更完整後,再來修改 ansible,目標是可以在 kvm 環境裡快速搭建一個完整且實用的 kuberentes 。


kubernetes cluster 安裝

從 kubernetes 1.14 之後,越來越容易用 kubeadm 安裝 cluster 了,基本的環境準備好之後,熟練的話可能十分鐘就裝好一個 kuberentes cluster (master * 3 + worker * 3)

為了簡化設定環境,可以把六台的 selinux 和 firewall 都先關掉。
安裝環境假設準備如下:

  1. 六台機器, OS 為 CentOS 7
    假設其 ip 為 192.168.1.11 ~ 192.168.1.16
  2. 選定一台當第一台 master,設定 master 可以直接以 ssh key 登入其他五台
    假設第一台 master 為 192.168.1.11
    另外兩台 master 分別是 192.168.1.12 及 192.168.1.13
  3. 準備一台機器設定好 haproxy ,做為三台 master node 的前端 proxy
    假設 haproxy 的 ip 為 192.168.1.10
  4. 六台機器都準備以下文件 (檔案內容放在文後)
    * /etc/yum.repos.d/kubernetes.repo
    第一台 master 準備以下文件 (檔案內容放在文後)
    * /tmp/kube-config.yaml
  5. 六台先進行如下設定
    # yum update && yum install -y yum-utils
    # yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
    # yum install -y docker-ce kubelet kubeadm kubectl --disableexcludes=kubernetes
    # systemctl enable --now docker
    # systemctl enable --now kubelet
    # swapoff -a
    * modprobe br_netfilter

到 192.168.1.11 (第一台 master) 開始安裝
# kubeadm init --config=/tmp/kube-config.yaml --experimental-upload-certs

一行指令就安裝完成了。

安裝完成後會提供一些訊息,其中有兩行分別是提供給其他 master 或 worker 加入 cluster,訊息內容類似如下:

kubeadm join 192.168.1.10:6443 --token 9vr73a.a8uxyaju799qwdjv --discovery-token-ca-cert-hash sha256:7c2e69131a36ae2a042a339b33381c6d0d43887e2de83720eff5359e26aec866 --experimental-control-plane --certificate-key f8902e114ef118304e561c3ecd4d0b543adc226b7a07f675f56564185ffe0c07
將這個指令複製下來到 192.168.1.12 及 192.168.1.13 上面執行,就可以將這兩台以 master 身份加入到 cluster 
kubeadm join 192.168.1.10:6443 --token 9vr73a.a8uxyaju799qwdjv --discovery-token-ca-cert-hash sha256:7c2e69131a36ae2a042a339b33381c6d0d43887e2de83720eff5359e26aec866
將這個指令複製下來到另外三台上面執行,就可以將這三台以 worker 身份加入到 cluster

要注意的是,兩個指令裡的 token 及 key 是有使用期限的,時間到了 token 和 key 就會被移除。所以如果一段時間後要將新的機器加
入 cluster ,就需要透過 kubeadm 指令重新產生 token 和 key 來使用。

不過在將其他台機器加入 cluster 之前,還有一個動作要先做,要指定及安裝 kubernetes cluster 內部使用的網路模式 (CNI),這邊以 weave 為範例,在第一台 master 上執行以下指令

# kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"


最後附上需要的設定檔

/etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
exclude=kube*

* /tmp/kube-config.yaml
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterConfiguration
kubernetesVersion: stable
controlPlaneEndpoint: "192.168.1.10:6443"
networking:
  podSubnet: 10.244.0.0/16

Dockerfile Best Practice

來源是從這份 簡報

這份簡報主要提出改進的目標如下:
  • build time
  • image size
  • maintainability
  • security
  • consistency/repeatability
簡報開頭先建議使用 buildkit 功能
先摘錄一下官網路個人認為比較重要的部分:
  • docker build 新選項
    --secret 可以避免在 Dockerfile 中直接寫入密碼
    --ssh 可以讓 build 出來的 image 直接有 ssh daemon
  • 使用 buildkit
    從環境變數打開 : DOCKER_BUILDKIT=1 docker build .
    修改設定檔: /etc/docker/daemon.json : { "features": { "buildkit": true } }

然後簡報開始針對上面的目標提出改善方式
  1. 減少 build time
    a. 善用 cache ,把比較容易變動的指令放到後面
    b. 指定 copy 的範圍可以減少 cache 使用,例如 
        COPY . /app  改成 COPY target/app.jar /app
    c. 避免使用到過期的套件,例如將 apt-get update 和 install 合併為一行
        apt-get update && apt-get -y install ssh vim
  2. 減少 image size
    a. 移除非必要的套件, apt-get -y install --no-install-recommends
    b. 移除安裝套件的 cache , rm -fr /var/lib/apt/lists/*
  3. 維護性
    a. 盡量使用官方出的 image
    b. 盡量指定 image 的 tag ,例如 FROM openjdk:8
    c. 從官網尋找符合需求下 size 最小的 image tag
  4. 重複使用性
    a. 盡量維持 build 環境的一致性
    b. 透過 multi-stage build 來減少 build iamge 時的相依性

簡報提出 multi-stage 的適用情境
  • separate build from runtime envrionment ( 降低 image size )
  • 降低 image 的變動
  • Build /dev /test /lint /... specific envrionments
  • 將相依性變成非線性化 (concurrency)
  • Platform-specific stages

使用 multi-stage 情境的範例
  • 用 --target 來指定要 build 的 stage

    Dockerfile:
    From image_or_stage AS stage_name

    Build Command: 
    docker build --target stage_name
  • use global Arguments

    Dockerfile:
    ARG flavor=alpine
    FROM openjdk:8-jre-$flavor AS release

    Build Command:
    docker build --target release --build-arg flavor=jessie .
  • buildkit 可以在 build 時排除非必要的 stage,並且可以多個 stage 同時進行 build

Dockerfile 新的語法 (experimental) ,以下語法都是在 docker v18.09+ w/ BuildKit 使用
  • context mounts
    COPY . /app => RUN --mount=target=. mvn -e -B package -DoutputDirectory=/
  • cache dependencies
    RUN --mount=target=. --mount=type=cache,target=/root/.m2 && mvn package -DoutputDirectory=/
  • serects

    錯誤的方法:
    (1) ENV AWs-ACCESS_KEY_ID  (直接寫在 Dockerfile)
    (2) ARG AWs-ACCESS_KEY_ID  (會保存在 docker history)

    建議的方式:
    Dockerfile:
    RUN --mount=type=secret,id=aws,target=/root/.aws/credentials,required

    Build Command:
    docker build --secret id=aws,src=~/.aws/credentials .
  • 使用 private git repos

    錯誤的方法:
    COPY ./keys/private.pem /root/.ssh/private.pem

    建議的方式:
    Dockerfile:
    RUN --mount=type=ssh,required git clone git@xxx:/repo /work

    Build Command:
    docker build --ssh=default .