2019年8月8日 星期四

master node 掛掉時的處理方式

寫這篇其實是完全不在計劃內,也是千百個不願意,因為早上不曉得什麼原因,某台 master node 掛掉了,請熟悉 KVM 的同事大大幫忙看,診斷結果是 image 也就是該 vm 的硬碟爛掉了,宣告不治。

以前處理過 worker node 掛掉的情況, worker node 簡單,掛掉了直接 delete node 然後裝一台重新 join 就好,心神無傷 (其實還是費工啦,不過至少用 vm 裝是輕鬆許多)。

之前看文件有印象是說,master node 也差不多,etcd 會自動 sync 。補充說明,我安裝 kubenretes cluster 的是用最基本的方式,用 kubeadm 把 master node 和 etcd 一起裝。

所以下午聽到噩耗時,想說那就面對吧。然後就....面對到錯誤訊息.....

先處理 join 的語法,到殘存的 master node 上用 kubectl 取得 join 資訊
# 取得 certificate key
kubeadm init phase upload-certs --experimental-upload-certs
# 取得 token 順便印出 join 語法
kubeadm token create --print-join-command

然後到新的 master node 上,用上面兩個資訊組合來 join 成 master node
kubeadm join :6443 --token --discovery-token-ca-cert-hash --control-plane --certificate-key

第一個錯誤訊息是....沒記錄到....  QQ
不過原因很間單,目前裝好的 kubernetes 版本是 1.15 ,而 vm 裡預設的好像是 1.13,升級 kubeadm 就好。
yum install -y kubeadm-1.15.0-0 kubelet-1.15.0-0 kubectl-1.15.0-0 --disableexcludes=kubernetes

第二個錯誤訊息類似是 "check-etcd error syncing endpoints with etcd"
於是我就找到這篇文章,裡面列出三部曲,照著做就可以練成神功,引刀自宮。

  • Removes the unhealthy etcd member C
  • kubectl delete node C
  • Updates the ClusterStatus in the configmap to remove C

這三部曲可以不用按照順序,事實上我一開始就已經自宮掉了 node C (第二步)

第一步是要把 etcd 自宮掉,但是老天鵝啊,考完 CKA 後我就沒再用過 etcdctl  指令了。
還好有筆記有保佑,沒花太多時間
# 先列出所有 etcd member ,然後找出屬於掛掉的 master node 裡的 etcd member
ETCDCTL_API=3 etcdctl --cacert="/etc/kubernetes/pki/etcd/ca.crt" --cert="/etc/kubernetes/pki/etcd/ca.crt" --key="/etc/kubernetes/pki/etcd/ca.key"  member list
# 移除該 etcd member
ETCDCTL_API=3 etcdctl --cacert="/etc/kubernetes/pki/etcd/ca.crt" --cert="/etc/kubernetes/pki/etcd/ca.crt" --key="/etc/kubernetes/pki/etcd/ca.key"  member remove 3290e2fc2debfa69
第三步看起來很令我狐疑,其實就是 kube-system namespace 裡的一個 configMap ,要把原本 master node 的資訊從裡面移除
kubectl -n kube-system edit cm kubeadm-config
就這樣,再跑一次 join 指令就搞定了。

好像沒事了? 我默默地為我留在舊的 master node 上的筆記哭泣、滴血。
因為我原本都是在那台 node 上操作的,套件安裝的一些設定、資訊、臨時筆記都留在上面............

設定 prometheus 的 backend 使用 influxdb

其實流程上很簡單,主要是對 influxdb 不熟,為了驗證資料是否有進去 influxdb 還試了一陣子語法,所以筆記一下。

先講環境。

prometheus 是用  helm  裝 prometheus-operator 版本,安裝在 monitoring namespace,名稱用 prometheus。

influxdb 是用 helm 裝 stable/influxdb 版本,安裝在 influxdb namespace,名稱用 influxdb。所以對其他 namespace 的 app 來說,要 access influxdb 的 url 就是 http://influxdb.influxdb:8086/。

另外,在安裝 influxdb 時, 設定了連入的帳號密碼,假設都是 admin。
然後到 influxdb 裡,建立一個dabase 給 prometheus 使用,名稱就用 prometheus
kubectl -n influxdb exec -it influxdb-774cbf58d6-8nrkp bash

進到 container 裡面後,執行 influx ,然後 create database prometheus 就好了。

接著修改 prometheus 的設定,參考 prometheus 官網的說明修改
kubectl -n monitoring edit prometheus prometheus-prometheus-oper-prometheus

找到其中的 remoteRead 和 remoteWrite 那兩行,修改為
 remoteRead:
  - url: http://influxdb.influxdb:8086/api/v1/prom/read?db=prometheus&u=admin&p=admin
  remoteWrite:
  - url: http://influxdb.influxdb:8086/api/v1/prom/write?db=prometheus&u=admin&p=admin

就這樣,搞定。剩下是驗證的部分......


可以先確認一下 prometheus pod 的 log,log 可能很長,篩選和 influxdb 有關的就好
kubectl -n monitoring logs prometheus-prometheus-prometheus-oper-prometheus-0 prometheus |grep influxdb

如果權限不對之類的而無法放資料到 influxdb ,可以從 log 裡看到。
設定正確的話,應該會看到類似這樣的 log
ts=2019-08-08T06:40:38.624Z caller=dedupe.go:111 component=remote level=info queue="0:http://influxdb.influxdb
:8086/api/v1/prom/write?db=prometheus&u=admin&p=admin" msg="starting WAL watcher" queue="0:http://influxdb.inf
luxdb:8086/api/v1/prom/write?db=prometheus&u=admin&p=admin"
我驗證的方式是在 monitoring 這個 namespace 跑一個 influxdb client 去測試連線及語法查詢。
kubectl -n monitoring run -it influx --restart=Never --image=influxdb --rm=true bash

進到 container 後,先用 curl 指令測試能不能遠端讀寫。
# write data
curl -i -XPOST 'http://influxdb.influxdb:8086/write?db=prometheus' --data-binary 'cpu_load_short,host=server01,region=us-west value=0.64 1434055562000000000'
# read data
curl -G 'http://influxdb.influxdb:8086/query?pretty=true' --data-urlencode "db=prometheus" --data-urlencode "q=select * from \"cpu_load_short\""


接著連到 influxdb namespace 裡的 influxdb 驗證資料是否有進來
influx -database prometheus -host influxdb.influxdb

然後查詢是否有 prometheus 的資料進來,我不知道有什麼可以查,所以先去 prometheus 本身的管理介面,隨便選一個 mertric 來查詢
select * from ceph_mds_metadata
看到有資料進來了,結案收工。

2019年8月7日 星期三

透過 dex 取得 kubernetes 認證 (4)

這篇來補充一下前幾篇漏掉的事項。

第一個是 token 過期,要 refresh token 的需求。在取得 id_token 的同時, dex 也會提供一個 refresh 用的 token。這個要分 web 或是 command line 的情況。

如果是 web ,這部分我還沒實作,不過看起來應該也是到取得 token 同樣的網址下,用 resfresh token 去更新 id_token。

如果是 command line,在 kubectl config set-credentials 的參數就需要加上一堆 oidc auth-provider 相關的資訊。官網上有詳細的資訊,這部分我沒有實作,因為覺得這種作法太不合人性了   XD

網路上有很多文章用 dex-k8s-authenticator 來簡化設定 kubectl 的流程,我還沒有試過,可以參考看看。


第二個是要撤銷 token ,也就是 revoke token 的需求。
目前似乎沒有現成簡易的方法可以做到,先保留這項需求。


另外補充一些之前沒提到的訊息。

* dex 有提供一個 endpoint 可以看到一些資訊,例如支援哪些 claims,以前兩篇文章內的例子來說,endpoint 會是 https://dex.example.com:32200/.well-known/openid-configuration

* dex 在 v2.10  後的某一版開始,response_type 支援比較多種的參數,以因應不同的流程。我是從 v2.10 直接跳到 v2.17,在 v2.10 的時候, response_type 只能設定為 "code",前面提到所有 RP 都是以 response_type 為 code 的情況下所完成的流程。新版的 dex 還支援 response_type 為 "id_token" 及 "token" 的選項,這部分要再找資料看流程上的差異為何。

先寫到這,還有一些細節想不起來,才隔一天多而已  orz

2019年8月6日 星期二

透過 dex 取得 kubernetes 認證 (3)

先複習官網關於 OpenID Connect Tokens 章節和這份之前提到的簡報的這頁
要讓 kubectl 之類的 command line tool 能透過 id_token 進行操作,要設定三個地方。

第一個是先用 kubectl 設定 ~/.kube/config 裡的 context。先假設已經設定好 cluster ,且 cluster 名稱為 kubernetes ,只需要設定 user 和 context 的部分。
kubectl config set-credentials dex --token=
kubectl config set-context dex --cluster=kubernetes --user=dex --namespace=default
kubectl config use-context dex

user 和 context 名稱隨便設定,兩者有對應起來就好,我這邊是都用 dex。設定完之後可以檢查一下 ~/.kube/config 確認設定是否正確。
這時用 kubectl 試看看,例如 kubectl get nodes,會出現類似 「server 沒有相關資源」錯誤訊息。


接下來是 kubernetes 的 APISERVER,如果是用 kubeadm 安裝的,設定檔會在 /etc/kubernetes/manifests/kube-apiserver.yaml。簡報那頁裡的三個設定項是必要的,另外還要設定 dex 使用的金鑰憑證 (ca.pem),最後因為我設定的 OpenID scope 裡還包含 groups ,所以還要加上 groups claim 的參數,所以最後在 APISERVER 加上的參數是

    - --oidc-issuer-url=https://dex2.example.com:32200
    - --oidc-client-id=php-oidc
    - --oidc-username-claim=email
    - --oidc-groups-claim=groups
    - --oidc-ca-file=/etc/kubernetes/pki/oidc.pem

最後一個參數要說明一下,為了方便辨識,所以把 ca.pem 更名為 oidc.pem,而要注意的是 kube-apiserver 是個 static pod,所以要把檔案放在這個 pod 能讀取到的路徑,這部分在之前有關 kubernetes audit 的文章內有說明過。

確認 kube-apiserver 重啟且運作正常後,再用 kubectl get nodes 試看看,如果設定正確的話,會出現不一樣的錯誤訊息,類似「使用者 xxx 沒有 list nodes 資源的權限」。很好,這表示 APISERVER 可以驗證出 token 裡的身份權限。


最後就是設定 kubernetes RBAC 權限,讓 token 裡的 user/group 可以有權限操作 kubernetes。因為我有設定 groups claim,所以 APISERVER 是可以認得的。另外要注意的是 groups claim 雖然是 Array 形式,但是在比對 RBAC 權限時,會比對陣列裡的每一個項目。

為了測試方便,設定用現有的 clusterrole ,例如 cluster-admin 。而我設定 LDAP 傳回的 groups 是一個只有包含名稱為 brobridge 的陣列,所以設定如下的 RBAC yaml
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: managers-oidc
subjects:
- kind: Group
  name: brobridge
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io

建立這個權限後,再用 kubectl get nodes 測試看看,可以了!


以上只是基本的設定,從官網和簡報中可以知道,在授權的部分 (即 Authz ),可以透過 websocket 方式來取得權限,就不需要自行根據每個 user/group 去寫 yaml 來設定權限。
不過這部分和 dex 比較沒有直接關係,而且這方式通常要接外部現有的系統,我這邊沒有類似的環境,所以就沒有進行了。

另外這幾篇也沒有提到 token 過期時,要 refresh token 的方法。
下一篇會討論需要補強及一些細節的地方。

透過 dex 取得 kubernetes 認證 (2)

用 PHP 實作 RP 這件事,對我來說其實分為兩個階段。

一開始我是希望能以 command line 的方式去進行 OIDC 認證,所以用 shell script 的方式取得 id_token。

後來是因為想弄一個可以管理 kubenretes 的 web 介面,希望在登入的部分是用 OIDC,也就是這個 web 就是一個 RP。因為對 golang 不熟,因此無法再從 example-app 來改,所以選擇用比較熟悉的 PHP 來做。

而在後面那個階段完成時,我才發現我第一階段做的 shell script,雖然可以 work ,但是有一些流程我其實沒搞清楚,所以先從第一階段開始說。

用瀏覽器的工具,可以觀察到從 example-app 到 dex 登入再回到 example-app 的流程裡,主要會有幾個頁面:

1. example-app 本身的登入頁面
2. dex 的登入頁面
3. dex 的 approval 頁面
4. 回到 example-app 顯示取得 id_token 的頁面

寫 script 的時候,以上動作都可以用 curl 辦到:
1. 從瀏覽器觀察到所傳遞的參數,設定給 curl ,就可以取得 dex login 頁面的 form 裡面參數 req
2. 將 req 參數及認證資訊 (也就是登入 ldap 的帳號密碼) 以 curl form post 的方式做登入
3. 再用 req 參數以 curl 傳到 approval 頁面,取得 id_token

問題就在第 3 步。整個 script 流程都沒瀏覽到 example-app,我以為這方式就用不到 example-app 了。實際上在第 3 步理,我傳給 curl 的參數裡有個 -L,也就是會 follow redirect link,在這一步 dex 會轉址回到 example-app,取得 id_token 是 example-app 這時才做的事,所以 script 取得的 id_token 其實是 example-app 處理後的結果。

也就是說,在 approval 之後, example-app 會自行連線到 dex 取得 id_token,而不是透過瀏覽器,所以從瀏覽器看不出來。到後面用 PHP 實作時,我才發現我錯誤的地方。

用 PHP/Laravel 實作的部分,比照 script 的做法
1. 因為是 GET method,直接把參數組一組 redirect 到 dex,就會進到 dex 登入頁面
2. 因為是 web 介面,不需要用程式做登入動作,所以 login 後會自動過 approval 然後轉址回指定的 Laravel 頁面,也就是 statciClients 設定的 redirectURIs
3. Laravel 收到的只有 url 裡的 code 參數,到這邊我才發現我之前理解上的錯誤

在翻過 example-app 原始碼,再去翻 dex 的原始碼,終於找到 RP 主動連線 dex 所傳遞的參數有哪些:
* code : 就是上面 url 裡的 code
* grant_type : 用 authorization_code
* client_id :  staticClients 裡設定的 id
* client_secret : staticClients 裡設定的 secret
* redirect_uri : staticClients 裡設定的 redirectURIs

把這些參數用 POST 的方式,用 guzzle 套件送到 dex server 的 /token ,以前面的例子來說, url 就是
https://dex.example.com:32200/token
這時候就可以取得 id_token 了

然後依照 id_token spec 去解出需要的資訊即可,這部分可以用這份簡報,參照 jwt.io 的線上 jwt decode (id_token 即為 JWT 格式的 token) ,就大概知道怎麼處理了。

從解出來的資訊,就可以取得 user 及 group (如果 SP 有提供,且 dex.yaml 有設定好的話),就可以透過 OIDC 認證取得使用者的身份了。

這篇處理了 web 的部分,下一篇將說明如何讓 command line tool (e.g. kubectl) 也能以取得的 token 來取得操作 kubernetes 權限。

透過 dex 取得 kubernetes 認證 (1)

官網可以看到 kubernets 支援幾種認證方式,其中一種叫 OpenI
D Connect ,也就是 OIDC。而最常用在 kubernetes 裡作為 Identify Provider 的軟體是 dex。

OIDC 的架構裡,有好幾種角色,這邊沿用 OpenID 官網 的 FAQ 裡的定義,以便後續使用說明。

* IDP : Identify Provider,在這篇文章中指的是 dex
* RP : Relying Party, 也就是需要取得認證的 application,在這篇文章中會用 php/laravel 來實作這角色。
* SP : Service Provider,實際上提供身份驗證的來源,OpenID 官網 FAQ 沒有用 SP 這個縮寫,只有提到 Service Provider,這裡為了方便所以用 SP,在這篇文章使用的 Samba AD。

dex 本身有提供一個 example app (名稱就叫做 example-app) 當作 RP 的角色來使用,我一開始入門 dex 就是用  example-app ,這部分在 dex 官網有還算清楚的設定流程。大致上摸索一下,用 example-app 和 dex 完成整個 OIDC 認證流程應該不難。

用 go get github.com/dexidp/dex 抓下 dex project ,在 dex 目錄下,有幾個可能會改到的地方:

* cmd/example-app/ :這裡面是 example-app 的 source,如果要寫一個自己的 RP,用 example-app 會是一個不錯的起點,連我這種 golang 門外漢都可以稍做修改。
* examples/k8s/ :這裡面提供 deploy dex 到 kubernetes 的 sample yaml ,以及產生金鑰的 sample script,我都是用這邊的來產生金鑰及安裝 dex。
* web :dex 的登入頁面的 source,如果只是要修改畫面的話在這邊修改即可。
* Dockerfile :如果需要改 dex 的 source,可以在編譯後用這個產生 dex image ,然後放到 image registry 上,然後在 deploy dex 的 yaml 指定 registry 上的 image。

另外在 deploy dex 前有一些預備動作,dex 預設會以 nodeport 的方式提供服務,而最好有一個 FQDN 指向 dex ,所以需要在 DNS 那邊先設定好,假設叫 dex.example.com。


修改 examples/k8s/gencert.sh 這個產生金鑰的 sscript,把 DNS.1 指定為 dex.example.com,然後執行 script ,產生的金曜會放在相對路徑下的 ssl 裡。
然後將產生的金鑰放到 kubernets 的 secret 裡
kubectl create secret tls dex.example.com.tls --cert=ssl/cert.pem --key=ssl/key.pem

注意一下上面指令裡的 secret name,在設定 deploy dex 的 yaml 會用到。

接下來是設定 SP 相關的部分,我一開始對 ldap/AD 不熟,選擇用 github/gitlab 來當 SP,基本上照著官網去設定 github/gitlab 取得 client-id 和 client-secret ,然後一樣也是放到 kubenrets 的 secret 裡
kubectl create secret generic github-client --from-literal=client-id= --from-literal=client-secret=

如果用 LDAP ,那 client-id 和 client-secret 就是登入 LDAP 的帳號密碼。用 LDAP 比較麻煩的是設定 yaml 的時候,因為對 LDAP  不熟,為了從 LDAP 取出 group 並對應到 yaml 裡的設定,花了不少力氣。

然後開始修改 examples/k8s/dex.yaml ,主要修改幾個地方:

1. Deployment 裡的 secretName ,也就是前面提到的 dex.example.com.tls 這個名稱。

2. ConfigMap 的 config.yaml :
   * connectors 是設定有哪些 SP 及相關的參數。
   * staticClients 設定有哪些 RP 可以來取得認證。
   * enablePasswordDB,如果不需要,可以改成 false,以提高安全性。

其中 staticClients 的設定的內容,和 RP 如何取得認證有直接的關係,這部分後面會說明。
設定完之後,就可以 deploy dex 了
kubectl apply -f dex.yaml

預設 replicas 是 3,也就是會有三個 dex pods。
執行 example-app 的部分,主要帶入的幾個參數是
* --listen 也就是 example-app 會 listen 哪個 ip:port
* --issuer IdP 也就是 dex 的 ip:nodeport
* --client-id 在 staticClients 裡設定的 id
* --redirect-uri  在 staticClients 裡設定的 redirectURIs

用瀏覽器打開 example-app 所 listen 的 ip:port ,就可以開始 OIDC 的認證流程了。

這篇先寫到這邊,下一篇會開始用 PHP/Laravel 去實作一個 RP。

2019年7月8日 星期一

MySQL 效能及安全性檢查

先使用別人提供的工具

因為我是自行以 source 安裝 mysql ,所以在本機連線時,需要指定 mysql.socket 的路徑,並且 mysqladmin/mysql 這兩個指令必須在 $PATH 可找到的路徑內,然後帳號密碼是用 mysql_config_editor 儲存,並且必須以 --login-path=client 方式來提供給這個 perl script 使用。所以我執行的參數如下
mysqltuner.pl --socket /MySQL/inst1/mysql.sock --output /tmp/health.txt
如果只想要輸出可能有問題的項目,可以加上參數
mysqltuner.pl --socket /MySQL/inst1/mysql.sock --nogood --noinfo

另外這個 script 執行時會去檢查兩個檔案是否存在,basic_passwords.txt  及vulnerabilities.csv。

前者是用一些常見的帳號密碼組合,來檢查系統中是否有太容易被嘗試就取得登入權限的帳號。後者是針對 mysql 版本的 CVE 記錄來檢查,看起來應該只是單純比對 mysql 是否符合 CVE 所記錄的版本號。

如果要用這個 perl script 作為定期檢查的工具的話,可能需要定期調整這兩個輔助檔案的內容,才比較能發揮效果。

在跑出來的建議裡,有幾個項目引起我的注意。第一個是
innodb_log_file_size should be (=256M) if possible, so InnoDB total log files size equals to 25% of buffer pool size

這句話關係到三個參數:
* innodb_log_file_size
* innodb_log_files_in_group
* innodb_buffer_pool_size。

這三個參數都可以從 SHOW VARIABLES like "%innodb%" 取得,其中關鍵是 innodb_log_file_size 這個值。

我這邊 my.cnf 裡相關的設定值是
innodb_buffer_pool_size = 2048M
innodb_log_file_size = 2048M
innodb_log_files_in_group 則是維持系統預設值 2
而這個 perl script 提出的建議公式是
log file size * log files group / buffer pool size = 0.25
=> log file size = buffer pool size * 0.25 / log files group
=> log file size = 2048M * 0.25 / 2 = 256M
所以得出上面那句敘述。
那這樣的公式是否有任何有效的憑據? 我查了幾篇文章

官網 (針對 version 5.7) 上的並沒有任何建議的公式,主要提供如下說明
log file size 如果設定的太小,會造成太多非必要的檔案寫入動作,如果設定太大,則會造成在災後復原的時間太久。
關鍵性的一句是
Make your redo log files big, even as big as the buffer pool.


一篇在這個原則下,採取以下計算方式作為建議。
從 show engine innodb status 取得 Log sequence number,然後看一分鐘的時間內,寫了多少 log size。文章的案例是一小時大約寫入 110MB,於是作者建議 log file size 只需要 128MB 就足夠,因為 log files group 是 2 ,所以最後 log file size 設定為 64MB。
這種計算方式沒有考慮 buffer pool size,純粹以 log 寫入的速度,及盡可能縮短災後復原的時間來做考量。

另外一篇則是認為,log file size 設定大一點不是問題,文章中的案例是 pool size 為 64G,log file size 設定為 15G, log files group 則是預設值 2 。以這樣的參數來看,比前面 perl script 建議的 0.25 高 (這篇算起來是  15 * 2 / 64 約等於 0.5)。這篇另外提供一個不錯的參考數值, 10GB 的 log file size 大約需要使用 20min 來復原,25GB 的 log file size 則是使用約 45min 來復原。也就是說,如果這個復原所需的時間是可以接受的,那 log file size 可以設定大一點。

所以綜合以上幾篇來說,我覺得可以不依循 perl script 的建議,官網都說了盡可能讓 log file size 大一些,甚至和 buff pool size 一樣大。

另一個引起我注意的建議項目是
innodb_buffer_pool_instances(=2)
關於這個參數的解釋可以看這篇,而我直接去看這個 perl script 的 source ,它的計算建議方式很簡單,如果 pool size 小於 1G,那就設為 1 個 instance,否則估算每一個 instance 可以有 1G 的 pool size 可以使用,也就是說 pool size 設為多少 GB (上限 64GB),就設定多少個 pool instances 。因為我這邊的 pool size 設定為 2GB,所以 pool instances 數量也就建議為 2 了。不過這應該還要搭配其他數據來看,而且從其他文章來看,當 pool size 足夠大的時候,這個設定值對效能的影響就沒有很明顯。

其他一些建議值,像是 query cache 的部分,因為這台 mysql server 是 lab 測試用的,平常沒有資料在上面跑,所以可能要找運作中的系統來跑測試,看看建議值是否會有所不同。

2019年7月5日 星期五

istio 的安裝設定調整 (4)

istio 在服務安全性的部分有個機制叫 mtls (mutual TLS) ,也就是服務之間的雙向 TLS 認證。

要打開 mtls 支援很簡單,把 values.yaml 裡的 mtls 設為 enable 就好,然後就可以用 istio 提供的工具 istioctl 驗證一下服務之間是否真的有 mtls
$ istioctl -n bookinfo authn tls-check sleep-7d457d69b5-zlt65 httpbin.bookinfo.svc.cluster.local
HOST:PORT                                   STATUS     SERVER     CLIENT     AUTHN POLICY     DESTINATION RULE
httpbin.bookinfo.svc.cluster.local:8000     OK         mTLS       mTLS       default/         default/istio-system 

httpbin 這個 sample app 我是裝在 bookinfo 這個 namespace ,這個要打完整的 FQDN 才能正確查詢。

如果沒使用 cert-manager ,TLS 使用的是 citadel 自行產生的憑證,也可以設定用其他憑證來取代 citadel 產生的,這部分可以參考官網。如果有使用 cert-manager,也就是使用 SDS 來取得憑證,那在 istio-proxy 這個 container 的 /etc/certs 就沒有 citadel 產生的憑證,這部分一樣可以參考官網

enable mtls 很簡單,不過後續要考慮的問題就變多了。例如外部要來使用服務,但不支援 TLS 的時候,或是要使用到外部的服務,但是對方不支援 TLS 的時候,都會造成問題。

因為有可能並非整個 kubernetes cluster 都在 istio 的 service mesh 內,因此上面那段比較正確的講法應該是,"非 istio service" 與 "istio service" 之間的溝通。

當 "istio service" 往 "非 istio service" 請求時,可以設定一個 DestinationRule 來 disable 這條路徑的 mtls。事實上像 kuberentes apiserver 就沒有 istio 的 sidecar,所以預設會有一個 DestinationRule 是把通往 apiserver 的 mtls 給 disable ,這部分在官網有蠻清楚的說明。

對於已經上線的系統,可以透過 permissive mode來測試新增的 policy。
permissive mode 意思是該項 policy 並不會真的生效,但是可以透過適當的設定,使其在 telemetry 的 log 中觀察出區別。以上面官網提供的例子來說,在 rabcsampelog 這個 instance resource 裡,區分了 responseCode 和 permissiveResponseCode,前者是實際回應的 http response code,後者是 permissive 設定情況下應該會有的 http response code。也就是說,可以從 istio-telemetry 這個 pod 的 log 裡觀察 permissiveResponseCode ,就可以知道設定是否正確。

當 "非 istio service" 往 "istio service" 請求時,可以設定 service 同時接受 TLS 與明文的請求,也是使用 permissive mode

從上面範例裡的 rbac-permissive-telemetry.yaml 這個檔案中,可以看到幾個新面孔的 CRD,像是 instance、handler 等,可以參考這篇的說明。

mtls 相關的部分,在官網寫得還算蠻清楚的,這部分我比較沒有實際動手操作確認,只有透過 istioctl 確認 mtls 運作正確,然後把幾個範例中的 yaml 掃過一遍,以及針對比較陌生的 CRD 稍微查了下資料。

為了 mtls ,把官網 tasks 裡的 security 項目整個掃過一遍,這是我始料未及的,裡面有很多我沒想到過的層面。雖然沒有整個流程實作過,但大致上有個初步的了解,希望日後若有機會運用到的話,不會手忙腳亂  QQ

2019年7月3日 星期三

修改 prometheus-operator rules

上週開始,prometheus-operator 開始狂發 etcdHighNumberOfFailedGRPCRequests alert ,然後 slack 就一直狂叫,有時一天數百個訊息。

而另一個也有裝 prometheus-operator 的 kubernetes cluster 卻沒有這情況,原本以為是 cluster 的問題,後來查了一下網路發現這可能是這條 rule 的 bug,不過這無法解釋為什麼另一個 kubernetes cluster 就不會產生 alert。

不過決定暫時先手動拿掉這條 rule ,因為是 operator 的架構,可以很直覺的去修改 rule 內容
kubectl -n monitoring edit prometheusrules.monitoring.coreos.com prometheus-prometheus-oper-etcd

把有關 etcdHighNumberOfFailedGRPCRequests 的 rule 移除掉即可 (我其實是 comment rule,但是儲存更新後,就自動把 comment 掉的 rule 給移除了)

順便補充一個連結,說明如何新增自己的 rule,同樣是因為 operator 的架構,讓新增 rule 的方式也變得很有彈性。

istio 的安裝設定調整 (3)

來整理一下 istio 裡 tls、ingress 與 cert-manager 的設定,有些地方還沒弄清楚,不過先簡單做個筆記。

話說上週安裝時,istio 才更新到 1.2.0 ,這禮拜又變成了 1.2.2 ....

在講 istio 之前,先講一下 cert-manager 。

簡單來說,cert-manager 是一個可以自動產生或取得 ssl 憑證的套件。如果用 helm 安裝 cert-manager 的話,記得要先安裝 cert-manager 的 CRD (官網上有寫)。

這篇文章對於安裝設定 cert-manger 蠻完整的,如果用 letsencrypt 的話,差不多就是這樣了。不過很不幸的是,我用的是自己虛擬出來的 domain name,沒辦法透過 letsencrypt 取得憑證,所以要用自行產生憑證的方式來設定。

cert-manager 建立的 CRD 主要有 Issuer / ClusterIssuer / Certificates,前兩個是設定取得憑證的方式,最後一個則是要產生哪些 domain 的憑證。建立和產生憑證的流程稍後再談,先回頭看 istio。

istio 如果不搭配 cert-manager 而是自行產生憑證來使用的話,可以直接參考官網的做法。
不過每個網站都要自行產生憑證太麻煩了,既然 istio 支援搭配 cert-manager 的方式,那就來試看看吧。

搭配 cert-manager 的設定方式,可先參考官網

首先是要變更 values.yaml 裡的設定,主要在於 enable istio 的 ingress 和 cert-manager。
相關變更過的設定如下:
gateways:
  enabled: true
  istio-ingressgateway:
    sds:
      enabled: true
  istio-egressgateway:
    enabled: false
certmanager:
  enabled: true
  email: duan@example.com
global:
  k8sIngress:
    enabled: true
    gatewayName: ingressgateway
    enableHttps: true

上面這設定花不少時間測試,目前在 istio 1.2.2 是可以 work 的。重點是要 enable ingressgateway 的 sds ,而 istio-egressgateway 預設是沒啟用的,不過我在測試過程中曾經 enable 而造成問題,所以修改成 enabled: false

設定更新上去後,會發現 istio-ingressgateway 這個 pod 裡的 containeer 由原本的一個變成兩個,多了一個名稱為 ingress-sds 的 container。

sds 全名是 secret discovery service ,這是官網的介紹
這邊有一個坑,不過不會馬上發現,我是到所有東西都設定好後,發現連不上 https 網頁,去查資料才發現的。

檢查 istio-system namespace 裡的 CRD gateways.networking.istio.io 的 istio-autogenerated-k8s-ingress 的設定裡,有 https 的相關設定。我參考這篇文章後,把 https 的部分移除後,原本的問題就解決了。
kubectl -n istio-system edit gateways.networking.istio.io istio-autogenerated-k8s-ingress
# 移除以下部分
  - hosts:
    - '*'
    port:
      name: https-default
      number: 443
      protocol: HTTPS
    tls:
      mode: SIMPLE
      privateKey: /etc/istio/ingress-certs/tls.key
      serverCertificate: /etc/istio/ingress-certs/tls.crt
基礎建設的部分都好了之後,可以開始著手實際的設定了,首先設定 cert-manager 的 Issuer。如前面提到的,因為是虛擬的 domain name,所以要設定為自行產生的方式。
apiVersion: certmanager.k8s.io/v1alpha1
kind: Issuer
metadata:
  name: test-selfsigned
  namespace: istio-system
spec:
  selfSigned: {}

然後設定要產生哪個 domain name 的憑證,假設叫 bookinfo.example.com
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
  name: test-certificate
  namespace: istio-system
spec:
  secretName: test-certificate
  issuerRef:
    name: test-selfsigned
  commonName: bookinfo.example.com
  dnsNames:
  - bookinfo.example.com
  acme:
    config:
    - http01:
        ingressClass: istio
      domains:
      - bookinfo.example.com
設定完以後,檢查一下憑證是否有產生
# 檢查 status
kubectl -n istio-system describe certificates 
# 檢查是否將產生的金鑰放在指定的 secret 裡
kubectl -n istio-system get secrets test-certificate 
接下來就可以拿 istio 提供的 sample 來驗證一下了。先用 sample 裡的 httpbin ,然後替 httpbin 建立 gateway 及 virtualservice 。

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: httpbin-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - hosts:
    - bookinfo.example.com
    port:
      name: http
      number: 80
      protocol: HTTP
  - hosts:
    - bookinfo.example.com
    port:
      name: https-default
      number: 443
      protocol: HTTPS
    tls:
      credentialName: test-certificate
      mode: SIMPLE
      privateKey: sds
      serverCertificate: sds
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
  - "bookinfo.example.com"
  gateways:
  - httpbin-gateway
  http:
  - match:
    - uri:
        prefix: /status
    - uri:
        prefix: /delay
    route:
    - destination:
        host: httpbin
        port:
          number: 8000

在這邊簡單講一下 gateway、virtualservice 與 kubernetes 本身的 service 元件之間的關係。httpbin 這個 sample application 本身是 listen 80 port ,而它建立的 svc 是 listen 8000 port ,然後轉給 pods 的 80 port。

上面的 virtualservice 則是設定說 "bookinfo.example.com" 這個 url 會連到 cluster 內部名稱為 httpbin 的 host (實際上就是 svc name) 的 8000 port。

透過這樣的機制,讓後端的 app (以這個案例來說就是 httpbin) 本身不需要提供 tls/https ,而是在 gateway 的部分就處理掉了,也就是 ssl termination。

都設定完之後,可以用 curl 來驗證一下 https 的情況了
curl -k  https://bookinfo.example.com/status/418

用瀏覽器的話驗證的話,可以確認一下看到的憑證是不是 cert-manager 產生的。


2019年6月28日 星期五

istio 的安裝設定調整 (2)

istio 預設選項裡 kiali 和 tracing 是 disable 的,不過這次安裝我想測試這部分的功能,所以把 values.yaml 這兩個選項都 enable 起來試看看。另外為了測試 tracing 功能,所以安裝了 istio 提供的範例程式 bookinfo。其中 bookinfo 的瀏覽網址會是 istio ingress 的 ip 加上 /productpage ,例如 http://10.10.10.185/productpage

裝好後,應該會有兩個 UI ,分別來自 jaeger 和 kiali。為了方便測試,我把這兩個 UI 相關的 svc 的 type 都改成 LoadBalancer,這兩個 svc 名稱分別為 jaeger-query 和 kiali。

然後開始瀏覽 bookinfo 的 page ,發現 jaeger-query 的網頁完全沒有 bookinfo 的紀錄。
這邊是我遇到的第一個坑。

jaeger 分成三個元件,query (即 web UI)、agent、collector,從 istio 安裝的 namespace 裡可以看到三個對應的 svc ,即 jaeger-query、jaeger-agent、jaeger-collector。不過仔細一看,這三個 service 都對應到同一個 labels 的 pods (app: jaeger),也就是 istio-tracing。

這是因為 istio 預設使用的是 all-in-one 的 jqeger image,所以這三個 svc 其實都是同一個 pod 在處理 (其實不止這三個,zipkin 這個 svc 也是指向 istio-tracing 這個 pod) 。

不過問題不在這,問題在於 istio 預設的 tracing 採樣率設定為 1 ...%,也就是 100 個 requests 裡只取樣 1 次。這對於實際上線時是有必要的,但對測試 istio 本身的功能是很不方便的。所以要調整一下取樣率,來把它設定為 100%。

查了一下設定的地方,是在 istio-sidecar-injector 這個 configMap 裡的 traceSampling。不過因為是以 json 格式的方式儲存,所以直接修改 confgMap 不太方便。

比較簡單的設定方式,就是修改 values.yaml ,不過 values.yaml 看不出來設定的地方,所以要參考 istio 提供的 values-istio-demo.yaml 裡面 pilot 的部分,把 traceSampling 設定為 100.0,然後更新設定即可。

更新後,每次瀏覽 bookinfo 後,都可以在 jaeger 的 UI 上看到資料了。
接下來看 kiali 的 UI,在登入的部分遇到第二個坑。

進入到 kiali UI 遇到的第一個問題,它會顯示沒有設定帳號密碼之類的訊息。
kiali 會檢查有沒有 kiali 這個 secret,有的話會掛載起來當帳號密碼來源。所以最簡單的解決方式就是手動建立
kubectl create secret generic kiali -n istio-system --from-literal "username=root" --from-literal "passphrase=1234"

建立完之後,刪除 kiali pods ,讓它自動重建一個 pod ,這樣才會把 secret 掛載起來,然後就可以用設定好的資料登入了。

在處理完上面這些後,才發現在 values-istio-demo.yaml 的 kiali 的部分,有一個 createDemoSecret 的選項,所以看起來如果有先設定這個話,是可以自動建立好登入用的帳號密碼。
2019/07/03 補充:如果設定為 createDemonSecret,會自動建立帳號密碼都為 admin 的 secret。

登入後會出現訊息說找不到 grafana 和 jaeger service。官網是這樣說明的

This task does not discuss Jaeger and Grafana. If you already installed them in your cluster and you want to see how Kiali integrates with them, you must pass additional arguments to the helm command, for example:
$ helm template \ --set kiali.enabled=true \ --set "kiali.dashboard.jaegerURL=http://jaeger-query:16686" \ --set "kiali.dashboard.grafanaURL=http://grafana:3000" \ install/kubernetes/helm/istio \ --name istio --namespace istio-system > $HOME/istio.yaml $ kubectl apply -f $HOME/istio.yaml
不過上面沒有說明到的部分是,kiali 並不是直接取得 jaeger 和 grafana 的資料再顯示到瀏覽器上,而是提供網址讓瀏覽器自己用 ajax 的方式去 jaeger 和 grafana 要資料。

也就是說,上面設定的兩個網址,必須是瀏覽器認得的,而不是 kuberentes 內部的 cluster name or ip。

因為我把 jaeger 和 grafana 的 svc type 都改成 LoadBalancer,所以要設定一個讓瀏覽器可以認得的 url ,例如 http://10.10.10.190:16686/jaeger

修改完後,可以在 kiali 上看到 jaeger 產生的圖了,但是 grafana 還是不行。
查了又查,改了又改,發現 kiali 裡設定的 grafana url 怎麼改,都不會改變瀏覽器去取得 grafana 資料的路徑。看到網路上有人說這是 istio 裡 kiali 的 bug,所以這部分就暫時先不去管了。
2019/07/03 補充:雖然 kiali 都會出現找不到 grafana service 的訊息,但只要有設定好,還是可以出現圖。

另外,如果像前一篇提到的方式,沒有安裝 istio 本身的 prometheus 的話,kiali 也會出現連不到 prometheus 之類的訊息,所以要去設定 kiali 的 configMap,加上 prometheus 的 url。不過這邊是 kiali 直接去抓 prometheus 的資料,所以只要設定 cluster 內部認得的 domain name 即可,例如
    external_services:
      prometheus:
        url: http://prometheus-prometheus-oper-prometheus.monitoring:9090

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 .