【編者的話】這是介紹Kubernetes的第三篇,主要集中講述如何配置Kubernetes叢集以及作者在配置過程中遇到的問題。
在本系列文章的第一部分中,我們探討了容器、Docker以及這些技術如何重新定義行業中的基礎設施及其運營方式。第二部分文章則繼續討論,著眼點放在Kubernetes身上——包括Kubernetes是什麼以及擁有哪些能力。在今天的第三部分文章中,我將進一步闡述如何上手Kubernetes,同時提供與結構設計相關的一點參考意見。
命令(Command)
Kubernetes的叢集管理是通過kubectl
命令進行的,如果你使用Google Cloud,則會在SDK中自動安裝kubectl
命令列工具。雖然你可以完全使用這個命令列工具來配置、控制Kubernetes叢集,但是我還是十分推薦你使用單獨的配置檔案來配置叢集,因為你可以使用版本控制工具來追蹤每次對叢集配置的修改而且保證配置的統一,後面會著重介紹如何去做。
kubectl
命令集合包含了非常多的子命令來幫助你控制Kubernetes叢集的方方面面,如窺探當前叢集的工作狀態等等。下面是我覺得非常常用的幾個命令,並將其分類說明。
管理類(Management)
kubectl apply -f service-file.yml
apply
命令會收集配置檔案(service-file.yml)中所有的配置項資訊,並自動將配置檔案中的配置項跟當前叢集的執行配置做對比,然後自動將必要的更新應用到當前叢集中。kubectl rollout
所有通過apply
命令觸發的指令都會生成一個新的Rollout物件。通過使用kubectl rollout status
命令可以檢視最近的更新狀態、終止一個rollout或者回滾到上一個資源物件(Deployment,Pod,Service等)的版本。
窺探類(Introspection)
kubectl describe [pod,service,...] [resource]
這條命令可以檢視某個資源的詳細資訊,當錯誤發生的時候你必須首選考慮使用describe
命令來獲取錯誤描述資訊。kubectl logs [resource]
Kubernetes內的容器會傾向於把所有的日誌定向到STDOUT
上,kubectl logs -f
命令能讓你獲取一個資源(Pod/Container)最新的日誌。kubectl get [pods,deployments,services,...]
這條命令會將Kubernetes預設名稱空間中執行的資源資訊列印出來,如果要看特定名稱空間的資源資訊,則必須加上--namespace=[my namespace]
引數,如果檢視所有名稱空間的資源資訊,則使用--all-namespace
引數。kubectl exec
這條命令是對docker exec
命令的包裝,可以讓你執行容器內部的命令,如果pod中只有一個容器在執行則此命令可以作用在pod上如:kubectl exec -it [pod] /bin/bash
。我們可以使用kubectl exec -it [pod name] --/bin/bash
來進入一個執行中的pod,-i
用來啟動標準輸入流STDIN
,-t
將輸入流定向到TTY(偽終端)中,這樣就能模擬終端的bash命令操作了。當然如果一個Pod中啟動了多個容器,你可以使用-C[container-name]
引數來進入特定的容器中。
部署新映象
當前還沒有命令能夠讓一個Deployment自動將配置的Pod下的容器更新到最新的版本;但是為Kubernetes叢集更新容器版本又是一個非常常見的操作,這時你就必須想清楚更新容器的步驟了,我列出以下幾種方法:
- 將新的容器打上
:latest
的Tag,然後手動刪除執行中的Pod,然後Deployment會自動用最新的容器重新啟動Pod; - 更新服務的配置檔案(Deployment yaml),讓容器指向指向最新的container label,比如:redis-cache:9c713a,然後重新
apply
這個配置檔案到Kubernetes的叢集中,這種方式需要對配置檔案進行版本控制; - 手動更新Deployment下Pod的映象版本:
kubectl set image deployment/[service name] *=[new image]
。
我需要的方案是即能夠在Kubernetes中留下更改歷史,也不想因為老是去提交配置檔案的更改導致陷入版本混亂的泥沼。所以,我個人選擇第三種方案,我是這麼操作的:
- 所有Pod template的image都指向容器的
:latest
tag; - 新的容器被Push到註冊中心時除了打上
:latest
tag以外還要打上與程式碼git倉庫HEAD指標指向的Commit號相同的tag(如:9c713a); - 執行
kubectl set image deployment/[service name] *=[new image]
時填入git commit hash號標識的映象。
根據以上幾點,我可以獲得一些好處:
- 用
set image
命令來更新Pods的映象非常的優雅,因為可以使用kubectl rollout status
來跟蹤所有Deployment的Pods的更新狀態; - 當Pod因為某些原因被殺掉或者新的Pod上線,都會確保執行擁有最新程式碼的映象;
- 我可以在minikube中使用跟Kubernetes叢集中相同的docker registry而不用擔心會影響到生產環境的Pods;我一直到上線前都不會使用
:latest
tag的映象(注:在測試環境或者非生產環境使用git commit hash號來啟動容器,但是在生產時使用:latest
,因為作者在push映象時同時打了兩個tag)。
搭建本地開發環境
Kubernetes開發人員不僅提供了相當全面的文件可以參考,而且提供了minikube這個可以讓開發者在本地環境執行Kubernetes叢集的工具。minikube可以執行在多種不同的虛擬化環境下,它可以很容易的啟動一個全功能的,包含一個單一節點的Kubernetes叢集。當叢集啟動後,minikube提供了多種命令來訪問與窺探叢集執行情況,同時kubectl工具也是自動為minikube配置好的。最常用的命令莫過於:minikube service [service name] --url
這條命令能夠列印出本地叢集中配好的Service的訪問url地址。minikube dashboard
這會跳轉到一個web-based的叢集看板頁面,幫助你通過視覺化的方式瞭解叢集執行的狀況。
同時我建議將執行minikube的預設的VM配置加高一點,比如配置:4CPUs與8GB記憶體,使用VMWare Fusion 作為VM容器,例如可以使用如下命令:minikube config set cpus 4
minikube config set memory 8192
minikube start --vm-driver vmwarefusion
如果要讓kubectl退出對minikube的訪問,必須重新配置kubectl讓它連線到新的叢集;如下命令:kubectl config get-contexts
kubectl config use-context [cluster context name from above]
當然,如果要重新連線到本地的minikube,只需要使用這條命令:kubectl config use-context minikube
Service的配置
Service的配置檔案支援JSON與YAML,但是我推薦使用YAML,因為它比較容易讀寫,而且支援註釋,這對那些複雜的結構非常管用。
對於叢集的配置,Service往往是最先配置的,這就是所謂的Service-first配置法。具體的做法是,為每個要建立的服務分配一個單獨的資料夾,這個資料夾中包含了所有啟動這個服務的配置檔案。為了搜尋方面,我建議為每個Kubernetes的Service資源建立一個yaml配置檔案,可以這樣命名:[service name]-k8s.yml
,這個配置檔案中寫入所有這個Service在Kubernetes環境啟動的配置項。
如下是我配置一個Redis快取服務的目錄結構:
redis-cache/
Dockerfile
redis.conf
redis-cache-k8s.yml複製程式碼
以下是服務配置檔案redis-cache-k8s.yml:
“`apiVersion: v1
kind: Service
metadata:
name: redis-cache
labels:
role: cache
spec:
type: NodePort
ports:
- port: 6379
targetPort: 6379
selector:
role: cache
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: redis-cache
spec:
github.com/kubernetes/…
revisionHistoryLimit: 3
replicas: 1
template:
metadata:
labels:
role: cache
spec:
containers:
- name: redis
image: redis:3
resources:
requests:
cpu: 100m
memory: 1Gi
ports:
- containerPort: 6379 ```複製程式碼
然後通過執行apply -f service/redis-cache/redis-cache-k8s.yml
命令就能讓redis-cache服務在整個Deployment中跑起來。
容器與註冊中心
如何為容器打Tag
我現在為每個容器都打兩個tag分別是::latest
與對應程式碼git庫中的HEAD指標Commit的hash值如::9c713a
。很多人強烈推薦不要使用:latest
tag,原因在這裡(注:內容大概講docker registry並不會主動判斷一個映象是否是最新的,而是可以人為的隨意為一個老的映象打上latest tag的,所以很多人認為latest標籤的映象其實並不一定是最新的),但是我覺得這些都是講給那些只用latest標籤的人聽的。我為什麼這麼做的原因在上面“部署新映象”一節已經闡述。
確保Minikube能訪問GKE的Registry
我早期使用minikube遇到的問題是如何讓我的Pod有許可權從我的Google私有映象倉庫中拉取docker映象。當然,當我在GKE中使用Kubernetes叢集拉取映象是沒有問題的,因為當使用GKE時,所有的伺服器都自動被賦予了訪問Google私有映象倉庫的的許可權,但是作為本地執行的minikube就沒有許可權了。
解決方法是在Pod的spec節中使用imagePullSecrets
值。首先,登入Google Cloud,然後前往IAM並建立一個新的Service Account
並賦予Storage -> Storage Object Viewer
許可權,確保勾選“Furnish a new private key”選項,完事後會給你一個JSON檔案,這個檔案你需要本地儲存,是用於授權的;所有這些準備就緒後執行這段指令碼生成一個新的Secret資源:
`#!/usr/bin/env sh
SPATH=”$(cd $(dirname “$0″) && pwd -P)”
SECRET_NAME=${1:-docker-registry-secret}
CONFIG_PATH=${2:-$SPATH/localkube.json}
if [[ ! -f $CONFIG_PATH ]]; then
echo “Unable to locate service account config JSON: $CONFIG_PATH”;
exit 1;
fi
kubectl create secret docker-registry $SECRET_NAME
–docker-server “gcr.io”
–docker-username _json_key
–docker-email [service account email address]
–docker-password=”cat $CONFIG_PATH
” ${@:3} 這段指令碼會生成一個名為
docker-registry-secret的Secret資源,這個資源稍後要在Service的配置檔案中的
imagePullSecrets值中被引用,如下所示:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: redis-cache
spec:
…
template:
spec:
imagePullSecrets:
- name: docker-registry-secret
...
containers:
- name: redis
image: gcr.io/[google account id]/redis-cache:latest複製程式碼
…`
配置完成後應該就能從Google私有映象倉庫中拉取映象了。
Secrets資源
敏感的資料比如:密碼,認證碼與各種key,這些都是要做特殊處理並很容易出錯的資料。首先,如果將這些資料不加密的儲存在程式碼管理倉庫中,不管這個倉庫多麼機密,並不是一個保險的做法;但同時,你又必須把這些資料加密並儲存在某個安全的地方,並使用一個跟蹤系統對其做版本記錄與許可權控制。我使用了很多不同的加密、儲存方式,發現StackExchange的BlackBox非常好用。
BlackBox使用PGP/GPG(非對稱加密)加密方式對檔案進行加密,確保只有特定的使用者才能訪問加密的檔案。對一個使用者授權訪問某個資源只需這個使用者提供自己的GPG公鑰,而移除一個使用者的授權只需要在一些配置檔案中將其名字去掉即可;然後你要做的事就是告訴BlackBox哪些檔案需要加密,而其餘的工作交給BlackBox即可,被加密的配置檔案可以放心的放入git倉庫了。
將這些加密的資訊提供給Kubernetes需要一些額外的本地指令碼,因為Kubernetes將配置資訊以明文的方式儲存在etcd中。比如:我會對兩類檔案進行加密:1、YAML配置檔案,內部包含很多key-value形式儲存的敏感資訊(如:資料庫密碼等);2、一些公鑰檔案如:SSL Certificate或者其他祕鑰。然後,我使用rake來解密這些檔案,然後將解密的檔案應用到Kubernetes叢集中,使用命令:apply -f -
(注意:`-`表示STDIN)。
比如,我有個加密的YAML檔案,如下:---
rails:
secret_key_base: "..."
service_api_key: "..."
database:
username: "..."
password: "..."
然後匯入Kubernetes的Secret:raw_secrets =
blackbox_cat secrets/my-secrets.yml.gpg`
secrets = YAML.load(raw_secrets)
secrets.each do |name, values|
k8s_secret = {
“apiVersion” => “v1”,
“kind” => “Secret”,
“type” => “Opaque”,
“metadata” => { “name” => name },
“data” => {},
}
values.each do |key, value|
k8s_secret[“data”][key] = Base64.strict_encode64(value)
end
stdout, status = Open3.capture2(“kubectl apply -f -“, stdin_data: k8s_secret.to_yaml)
end 然後在Service的配置檔案中來引用這些Secret(使用Secret名稱,我通常使用環境變數來指定):
…
env:
- name: RAILS_SECRET_KEY_BASE
valueFrom:
secretKeyRef:name: rails key: secret_key_base複製程式碼
- name: RAILS_SERVICE_API_KEY
valueFrom:
secretKeyRef:name: rails key: service_api_key複製程式碼
- name: DATABASE_USERNAME
valueFrom:
secretKeyRef:name: database key: username複製程式碼
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:name: database key: password`複製程式碼
我認為這是我使用、配置Kubernetes過程中遇到的主要問題,希望對您有幫助。
原文連結:CONTAINERS, DOCKER, AND KUBERNETES PART 3(翻譯:肖勁)