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。