[好文介紹]Scaling Kubernetes to 2,500 Nodes

原文 Scaling Kubernetes to 2,500 Nodes

緣起

最近比較忙碌,都只能夜深人靜才能好好的來閱讀一些文章來充實自己.(公司牛人多到像牧場,只好不斷努力 lol )

OpenAI 最近有一篇技術文章,相當的值得一讀.就是他們分享他們如何管理超過 2500 個節點. 當然我們都知道,

Kubernetes 自從 1.6 之後就號稱可以乘載 5000 個節點以上,但是從 數十台到 2500 台的路上,難道不會遇到一些問題嗎? 這篇文章分享了,他們遇到的問題,試著要解決與懷疑的地方,最後找到真正的問題.這篇文章適合所有 DevOp 好好的熟讀.

遇到的問題與解決方式

第一次遇到問題: 1 ~ 500 個節點之後

  • 問題徵兆:
    • kubectl 有時候會發生 timeout. (p.s. `kubectl -v=6 可以顯示所有API 細節指令)
  • 嘗試解決方式:
    • 一開始以為是 kube-apiserver 服務器過分忙碌,試著增加 proxy 來做 replica 來幫忙 load balance
    • 但是超過 10 個備份 master 的時候,他們發現問題一定不是因為 kube-apiserver 無法承受.因為 GKE 透過一台 32-core VM 就可以乘載 500台
  • 原因:
    • 扣除掉這些原因,開始要懷疑 master 上剩下的幾個服務. (etcd, kube-proxy)
    • 於是開始試試看來調整 etcd
    • 透過使用 datadog 來調查 etcd 的吞吐量,發現有異常延遲 (latency spiking ~100 ms)
    • 透過 Fio 工具來做效能評估,發現只用到了 10% 的IOPS(Input/Output Per Second) , 由於寫入延遲 (write latency 2ms),造成整個效能被拖累.
    • 試著把 SSD 從網路硬碟變成每台機器有個 local temp drive (依舊是 SSD)
    • 結果從 ~100ms —> 200us

第二次遇到問題: ~1000 個節點的時候

  • 問題徵兆:
    • 發現 kube-apiserver 每秒需要從 etcd 上面讀取 500Mb
  • 嘗試解決方式:
    • 透過 Prometheus (一種廣泛用於 Kubernetes 上面作為 Container 資料搜集與監控工具) 來查看每個 container 之間的網路流量
  • 原因:
    • 發現 Fluentd (一個作為資料與 log 轉發的工具) 與 Datadog ,太頻繁來抓取每個節點上面的資料.
    • 調低這兩個服務的抓取頻率,網路效率馬上就從 500 Mb/s 到幾乎沒有…
    • etcd 小技巧: 透過 --etcd-servers-overrides 可以將 Kubernetes Event 的資料寫入作為切割,分不同機器處理.範例為 --etcd-servers-overrides=/events#https://0.example.com:2381;https://1.example.com:2381;https://2.example.com:2381

第三次問題: 當經營到 1000~2000節點

  • 問題徵兆:
    • 無法再寫入資料,回報 cascading failure
    • kubernetes-ec2-autoscaler 在全部的 etcd 都爆掉之後才回傳問題,並且關閉所有的 etcd
  • 嘗試解決方式:
    • 猜測 etcd 硬碟爆滿,但是檢查 SSD 硬碟空間依舊有許多空間.
    • 檢查是否有預設設定空間限制,發現有 2GB 大小限制
  • 解決方式:
    • etcd 啟動參數加上 --quota-backend-bytes
    • 修改 kubernetes-ec2-autoscaler logic 如果超過 50% 都出問題,預警性的關閉集群.

## 各種服務的優化

除了遇到的問題解決外, OpenAI 也提供他們一些設定方式. 可以幫助你更順利的在大量集群上的運作.

Kube masters 的 HA (High Availability)

一般來說,我們架設都是以一個 kube-master (主要的 Kubernetes 服務提供者,上面有 kube-apiserver, kube-schedulerkube-control-manager ) 加上多個 slave . 但是要達到 HA 設定,要根據以下的方式:

  • kube-apiserver 要設置多個服務,並且透過參數 --apiserver-count 重啟並且設定.
  • kubernetes-ec2-autoscaler 可以幫你自動關閉 idle 的資源,但是這跟 Kubernetes scheduler 的原則相反. 不過透過這些設定,可以幫助你盡量把資源集中.
{
"kind" : "Policy",
"apiVersion" : "v1",
"predicates" : [
  {"name" : "GeneralPredicates"},
  {"name" : "MatchInterPodAffinity"},
  {"name" : "NoDiskConflict"},
  {"name" : "NoVolumeZoneConflict"},
  {"name" : "PodToleratesNodeTaints"}
  ],
"priorities" : [
  {"name" : "MostRequestedPriority", "weight" : 1},
  {"name" : "InterPodAffinityPriority", "weight" : 2}
  ]
}

上面是個調整你 Kubernetes scheduler 的範例,這個範例會透過把 InterPodAffinityPriority 的權重調高,達到我們要的調整方向.更多的部分,可以參考原始範例

不過要注意,目前 Kubernetes Scheduler Policy 並不支援動態切換,需要重起你的 kube-apiserver (issue: 41600)

參考文件: VF技術部落格: 深入kubernetes调度之原理分析 – 關於 Kubernetes Scheduler 相關介紹文件(中文)

調整 scheduler policy 造成的影響

OpenAI 有使用 KubeDNS: 一個 Kubernetes 內部使用的 DNS ,但是經過不久發現

  • 問題徵兆:

    • 經常有 DNS 忽然查詢不到的 (隨機發生)
    • 超過 ~200QPS domain lookup
  • 嘗試解決問題方式:

    • 試著察看為何有這種狀態,發現有些 node 上面有跑超過 10+ KubeDNS
  • 解決方式:

    • 由於 scheduler policy 造成許多 POD 會儘量的集中.
    • KubeDNS 相當的 lightweight,所以很容易被分配到同一個節點.造成 domain lookup 集中.
    • 需要修改 POD affinity (這邊有更多相關介紹),儘量讓 KubeDNS 不要都分配到同一個節點 (node)
    affinity:
     podAntiAffinity:
       requiredDuringSchedulingIgnoredDuringExecution:
       - weight: 100
         labelSelector:
           matchExpressions:
           - key: k8s-app
             operator: In
             values:
             - kube-dns
         topologyKey: kubernetes.io/hostname
    

緩慢的 Docker image pulls (針對新建節點)

  • 問題徵兆:
    • 每次一個新的節點建立起來,都得花約 30mins 在 docker image pull
  • 嘗試解決方式:
    • 有一個相當龐大的 container image Dota, 裡面約莫 17GB.他影響了整個節點的 image pulling
    • 開始查看 kubelet 是否有其他選項可以讓你 image pull
  • 解決方式:
    • kubelet 加上選項 --serialize-image-pulls=false 來啟動 image pulling 來讓其他服務可以比較早 pull 參考:kubelet 啟動選項
    • 並且把 docker image 存放位置放到 SSD ,讓 image pull 更快

補充: 特地下去把 source trace 了一下

// serializeImagePulls when enabled, tells the Kubelet to pull images one
// at a time. We recommend *not* changing the default value on nodes that
// run docker daemon with version  < 1.9 or an Aufs storage backend.
// Issue #10959 has more details.
SerializeImagePulls *bool `json:"serializeImagePulls"`

增加 docker image pull 速度

此外,還可以透過以下方式來增加 pull 速度

  • kubelet 有個參數 --image-pull-progress-deadline 要提高到 30mins
  • docker daemon 有個參數 max-concurrent-download 要調到 10 才能多線程下載

網路效能的提升

Flannel 效能限制

OpenAI 節點間的網路流量,可以達到 10-15Gbit/s .但是由於 Flannel 所以導致流量會降到 ~2Gbit/s

解決方式就是將 Flannel 拿掉,使用實體網路.

  • hostNetwork: true
  • dnsPolicy: ClusterFirstWithHostNet

這裡有一些警告需要詳細閱讀

參考文章 :

[論文導讀]The Case for Learned Index Structures (二)

論文原文: The Case for Learned Index Structures

Morning Paper Reading: part1, par2

參考文章:

[論文導讀]The Case for Learned Index Structures (一)

論文原文: The Case for Learned Index Structures

Morning Paper Reading: part1, par2

緣起:

剛好最近有幾次機會可以去工研院開會的路上,在高鐵的路途上可以好好的來欣賞這篇文章. 這篇文章是由 Google Brain 的大神 Jeff Dean 連署的論文之一.講的是透過 NN 的方式來讓大家熟知的 B-Tree, Hashing table 甚至是 Bloom Filter 更有效率…

2018 年第一篇好好閱讀的論文,當然要獻給有深度而且相當有趣的這篇文章.The Case for Learned Index Structures .主要的原因有以下:

Jeff Dean 是誰? 快來看他的 Quorahttps://www.quora.com/What-are-all-the-Jeff-Dean-facts

- 他看得懂, 也寫 Binary code - 他的 PIN code 是 Pi 末四碼

先講講 B-Tree

B-Tree (Binary Tree) 是大家相當熟知的資料結構,在此僅列出幾個需要知道的.

  • 時間複雜度 (Balanced B-Tree)
  • 需要空間(cache) 代表存多少資訊在 B-Tree .當資料不在 cache 中,代表需要重新跑一次 re-balanced
  • B-Tree traversal 無法分散式處理 (透過 GPU 來加速)

再來談 Learning Index Tree

回過來講 B-Tree Index 你可以把一個數值輸入 B-Tree ,透過搜尋過後可以傳回一個 Index (可能有 re-balanced).

換個角度,如過透過 NN (Neural Networking) model 的學習將一個數值輸入後,來預測 (predict) 它可能的索引位置 (index) .那麼我們就稱這個為 Learned Index

這邊有一些你需要知道關於 Learned Index 的部分:

  • 由於直接運算,所以時間複雜度相當的低:
  • 由於透過 NN 來運算,可以很輕易透過 GPU 來加速運算
  • 空間要求相當的少
    • 不像 B-Tree Index 需要一定的空間來儲存目前已知的數值來加速. (根據文章: cache size 128 是最快的)

關於 CDF (Cumulative Distribution Function)

B-Tree 就可以當成是一個透過 Key 值來轉換到 Index (Pos)的方式. 那麼也可以當成是透過 Key 的分佈來預測 (predict) 位置.

Tensorflow 來做 CDF 與傳統 B-Tree Index 的效能比對

  • 來源資料: 200M 筆的 Web Log
  • Activation fnction: ReLu
  • 32 個 Neural per layer

透過 Tensorflow 來實作為 Naive Learning Index 與 B-Tree 的效能比對. B-Tree 快上 2~3 倍,論文提出以下理由:

  • Tensorflow 設計是為了處理大量的數據,對於 200M (相對小) 他本身的處理效率有相對的消耗
  • B-Tree 的結果會 overfit 也就是說完全以輸入的資料來作為 B-Tree 的計算與樹狀的建置. 但是相反的 CDF 是透過 NN 來計算”可能性”的位置.
  • B-Tree 需要比較大的 Cache (比較表內使用 128 的 cache 效能最佳) ,而 CDF 透過 NN 來學習不需要儲存之前的資料.(不過兩者都需要 re-train (CDF) 與 re-balanced(B-Tree) )

參考文章:

[TIL] 超好用的 Kubernetes Console tool - c9s/Vikube.vim

Kubernetes 相當好用,但是要維運的時候最痛苦的事情,就是要打 kubectl

雖然我超愛 Golang ,而且其實我基本的 IDE 都是使用 VSCode ,但是我一定要跟各位好好推薦這個好工具. Vikube.vim

不論你透過 alias 設定成 kc 甚至是 k 還是得要記憶一堆指令 ex: kubectl get pod

其實就算你不是 vim 的愛用者 (畢竟學習曲線太高了) 我還是很推薦你使用這個工具.

  • :VikubeContextList 可以開啟你所有連接過的 K8S 集群,透過 s 來切換你的集群.
  • :VikubeNodeList 可以開啟所有的節點清單,l 可以看到 結點上面的 logs, 可以幫助你除錯(如果有問題)
  • :VikubePodList 可以開啟 pod 清單,當然也可以透過 l 來看 POD 是否有出現問題.
  • :VikubeTop 可以開啟 top 來看各個 POD 的使用量

真的很好用… 再也不用擔心打 kubectl 或是忘記相關指令了 :p

[TIL] Change fish shell theme with nerd font

起源

Fish Shell 強大的親和力,並且讓我很簡單就上手.讓我使用上相當方便. 但是隨著許多軟體的安裝都還是需要 bash 並且在 vim 的更新上變得相當困難. 我其實有開始想要把 fish 換到大家比較常使用的 zsh .

原本看了這篇文章 超簡單!十分鐘打造漂亮又好用的 zsh command line 環境 要打算把自己的 fish shell 換到 zsh.

不過考量許多原因,做了一些小修改:

Fish shell failure update Vundle in Vim:

Refer: I don’t use a POSIX Shell (i.e. Bash/Sh)](https://github.com/VundleVim/Vundle.vim/wiki#i-dont-use-a-posix-shell-ie-bashsh)

解法: add ~/.config/fish/functions/updatevim.fish

function updatevim
    set -lx SHELL (which sh)
    vim +BundleInstall! +BundleClean +qall
end

Zsh PowerLevel9k 看起來實在很威

PowerLevel9K 實在很方便,所以找了一個 Fish Shell 可以使用的 bobthefish

不過 bobthefish 資訊太多了,需要一些調整.

#OMF setting for bobthefish
#
set -g theme_display_date no
set -g theme_nerd_fonts yes
set -g theme_display_k8s_context no

換漂亮的字型 nerd-font

後來有換這個字型,看起來還挺順眼的.

還有..

VSCode Integrated terminal 還是會有亂碼,目前還在解決中. 可以參考這個VSCode integrated terminal versus nerdfont-complete #672

Reference

[TIL][Google Kubernetes Engine] Tensorflow/k8s and GPU on GKE

Easy way for :Distributed Tensorflow

tensorflow/k8s : Tools for ML/Tensorflow on Kubernetes.

Architecture:

  • Run a job operator by Helm
  • Post your job just like a simple yaml file
  • Job Operator will help you to distributed to each node for computing result
    • PS
    • Worker

Sample Job Kubernetes YAML file

apiVersion: "tensorflow.org/v1alpha1"
kind: "TfJob"
metadata:
  name: "example-job"
spec:
  replicaSpecs:
    - replicas: 1
      tfReplicaType: MASTER
      template:
        spec:
          containers:
            - image: gcr.io/tf-on-k8s-dogfood/tf_sample:dc944ff
              name: tensorflow
          restartPolicy: OnFailure
    - replicas: 1
      tfReplicaType: WORKER
      template:
        spec:
          containers:
            - image: gcr.io/tf-on-k8s-dogfood/tf_sample:dc944ff
              name: tensorflow
          restartPolicy: OnFailure
    - replicas: 2
      tfReplicaType: PS

Sample Distributed Tensorflow Code

Refer here.

Troubleshooting: Could not enable default namespace on Helm.

kubectl create serviceaccount --namespace kube-system tiller
kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
kubectl patch deploy --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'      
helm init --service-account tiller --upgrade

GKE: Enable GPU on GKE

gcloud alpha container clusters create gpu-test \
    --project $PROJECT_ID \
    --zone $ZONE \
    --enable-kubernetes-alpha \
    --enable-cloud-logging \
    --enable-cloud-monitoring \
    --accelerator type=nvidia-tesla-k80,count=1 \
    --machine-type n1-standard-1 \
    --cluster-version=1.8.4-gke.1 \
    --image-type $IMAGE_TYPE \
    --num-nodes 1 \
    --quiet

Trobleshooting:

  1. K8S alpha don’t support master version upgrade, so you need define k8s 1.8 when you create it. (default: 1.7.8)
  2. How to get GKE current support versions?
    1. gcloud container get-server-config