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 就比較有把握了。