作者:Caleb Doxsey

翻譯:小君君技術校對:星空下的文仔、夏天編輯:小君君

Kubernetes 的穩健性、可靠性使它成為現階段最流行的雲原生技術之一,但也有不少用戶反映, Kubernetes 技術學習起來十分複雜,只適用於大集群且成本較高。這篇文章將打破你的觀念,教你在小型項目中部署 Kubernetes 集群。

選擇 K8S 部署小型集群的三大理由

理由一:花費時間少

在部署小型集群之前,你需要思考以下這些問題:

  • 應該如何部署應用程序?(僅僅 rsync 到伺服器?)
  • 依賴關係是怎麼樣的?(如果利用 python 或 ruby,你必須在伺服器上安裝它們!)
  • 手動運行命令?(如果以 nohup 的方式在後台運行二進位文件這可能不是最好的選擇,但去配置路由服務,是否還需要學習 systemd?)
  • 如何通過不同域名或 HTTP 路徑運行多個應用程序?(你可能需要設置 haproxy 或 Nginx!)
  • 當更新應用程序後應該如何推出新變化?(停止服務、部署代碼、重啟服務?如何避免停機?)
  • 如果搞砸了部署怎麼辦?有什麼方法可以回滾?
  • 應用程序是否需要使用其他服務?又該如何配置這些服務?(如:redis)

以上這些問題很有可能在你部署小型集群時出現,但 Kubernetes 為上述所有問題都提供了解決方案。或許還有其他方法可以解決上述問題,但是利用 Kubernetes 往往事半功倍,因為我們需要更多的時間專註於應用程序。

理由二:Kubernetes 記錄整個部署過程

讓我們看看利用 Kubernetes 部署集群的第二個理由。

你在工作時是否也是這樣的狀態:我上次運行了什麼命令?當時伺服器在運行什麼服務?這讓我想到了著名的 bash.org

<erno> hm. Ive lost a machine.. literally _lost_. it responds to ping, it works completely, I just cant figure out where in my apartment it is.

bash.org/?

這種情況曾經出現在我的工作中,讓原本 10 分鐘的工作量變成了一個周末。

但是如果你選擇 Kubernetes 部署集群,就不會有這種困擾。因為 Kubernetes 使用描述性格式,如此用戶就可以很輕鬆地知道接下來應該運行哪些內容,如何部署構建塊。此外,控制層也會正常處理節點故障並自動重新調度 Pod。(對於像 Web 應用程序這樣的無狀態服務,就不再需要擔心失敗。)

理由三:Kubernetes 簡單易學

Kubernetes 擁有自己的辭彙表、工具,以及與傳統 Unix 完全不同的配置伺服器。Kubernetes 的知識足以建立和維護基礎設施。使用 Kubernetes,你可以完全可以在 Kubernetes 中配置服務,無需 SSH 到伺服器。你不必學習 systemd 也不必知道什麼是運行級別; 你不必格式化磁碟,或學習如何使用 ps,vim。

我通過一個例子,來證明我的觀點!

這是利用 Unix :

[Unit]
Description=The NGINX HTTP and reverse proxy server
After=syslog.target network.target remote-fs.target nss-lookup.target[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/usr/sbin/nginx -s reload
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true
[Install]
WantedBy=multi-user.target

真的比利用 Kubernetes 難:

apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 1
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 80

當你遠程執行基礎架構管理時,是不需要手動維護伺服器的。你可以使用:ansible、salt、chef、puppet 等來完成這件事。當然,你需要學習使用很多 Kubernetes 相關工具,這要比學習替代品輕鬆的多。

小 結

Kubernetes 並不是將一件事做到極致的工具而是一個全方位的解決方案,它取代了開發人員習慣使用的許多技術和工具。

接下來我們用實際操作,為大家部署一個小型 Kubernetes 集群。

建立小型 Kubernetes 集群

下面就開始我們的教程。對於這個例子,我們將使用谷歌的 Kubernetes引擎(GKE),但如果谷歌不是你的菜,你也可以選擇亞馬遜(EKS)或微軟(AKS)。

要構建我們的 Kubernetes 集群,我們將需要:

  • 域名(10 美元 /年,具體取決於域名);
  • DNS 主機由 cloudflare 提供(免費);
  • GKE 中的 3 個 node kubernetes 集群(5 美元 / 月);
  • 將 Webapp 作為 Docker 容器發送到 Google Container Registry(GCR)(免費);
  • 一些 yaml 文件配置 Kubernetes。

此外,為了節省成本,我們不會使用谷歌的 ingress controller。相反,我們將在每個節點上運行 Nginx 作為 Daemon,並構建一個自定義運算符,將工作節點外部 IP 地址與 Cloudflare 同步。

谷歌設置

首先訪問 console.cloud.google.com 並創建一個項目(如果你還沒有項目)。你還需要設置結算帳戶。然後前往 hamburger 菜單中的 Kubernetes 頁面並創建一個新的集群。

你需要執行以下操作:

  • 選擇 Zonal 區域類型(我使用了 us-central1-a 作為我的區域);
  • 選擇你的 Kubernetes 版本;
  • 使用最便宜的實例類型(f1-micro)創建 3 個 node 池;
  • 對於該節點池,在高級屏幕中,將引導磁碟大小設置為 10GB,啟用可搶佔的 node(它們更便宜),啟用自動升級和自動修復;
  • 在節點池下面還有一些其他選項。我們想要禁用 HTTP 負載均衡(GCP 中的負載均衡很昂貴且不穩定)並且還禁用所有 StackDriver 的服務以及禁用 Kubernetes dashboard。

通過設置所有這些選項,你可以繼續創建集群。

以下是減少的成本:

  • Kubernetes 控制層:免費,因為谷歌不收取專家的費用;
  • Kubernetes 工作節點:5.04 美元/月,3 個微節點通常為 11.65 美元/月,通過使用它們的可搶佔性,我們將其降至 7.67 美元/月(「永久免費」等級則達到 5.04 美元);
  • 存儲成本:免費,存儲成本可以在 GCP 中累計。我們將免費獲得 30GB 的永久磁碟,這就是我們選擇 10GB 大小的原因;
  • 負載均衡器成本:免費,我們禁用 HTTP 負載均衡,因為僅此一項費用將達到 18 美元/月。相反,我們將在每個節點上運行我們自己的 HTTP 代理,並將 DNS 指向公共 IP;
  • 網路費用:免費,只要你每月低於 1GB,出口就是免費的(之後每GB 8 美分)。

因此,我們可以擁有一個 3 個節點的 Kubernetes 集群,價格與單個數字機器相同。

除了設置 GKE 之外,我們還需要添加一些防火牆規則,以允許外網點擊我們節點上的 HTTP 埠。操作是:從 hamburger 菜單轉到 VPC 網路,防火牆規則添加為 TCP 埠 80 和 443 的規則,IP 範圍為 0.0.0.0/0。

本地設置

隨著集群的啟動和運行,我們就可以對其進行配置。通過 cloud.google.com/sdk/do 的說明安裝 gcloud 工具。

安裝完成後,你可以通過運行以下命令進行設置:

gcloud auth login

你還需安裝 Docker,將其連接到 GCR 上,方便你進行容器推送:

gcloud auth configure-docker

你也可以按照此處的說明安裝和設置 kubectl (此工具適用於 Windows,OSX 或 Linux)。

gcloud components install kubectl
gcloud config set project PROJECT_ID
gcloud config set compute/zone COMPUTE_ZONE
gcloud container clusters get-credentials CLUSTER_NAME

構建 Web 應用程序

你可以使用任何編程語言構建 Web 應用。我們只需構建一個 port 埠的 HTTP 應用程序。就個人而言,我更喜歡在 Go 中構建這些應用程序,但對於某些類型,讓我們嘗試使用 Crystal。

創建一個 main.cr 文件:

# crystal-www-example/main.crrequire "http/server"Signal::INT.trap do
exit
end

server = HTTP::Server.new do |context|
context.response.content_type = "text/plain"
context.response.print "Hello world from crystal-www-example! The time is #{Time.now}"end

server.bind_tcp("0.0.0.0", 8080)
puts "Listening on http://0.0.0.0:8080"server.listen

我們還需要一個 Dockerfile:

# crystal-www-example/Dockerfile
FROM crystallang/crystal:0.26.1 as builder

COPY main.cr main.cr

RUN crystal build -o /bin/crystal-www-example main.cr --release

ENTRYPOINT [ "/bin/crystal-www-example" ]

我們可以通過以下命令來構建和測試我們的 Web 應用程序:

docker build -t gcr.io/PROJECT_ID/crystal-www-example:latest .
docker run -p 8080:8080 gcr.io/PROJECT_ID/crystal-www-example:latest

然後輸入 localhost:8080 在瀏覽器中訪問。接著我們可以通過以下方式將我們的應用程序推到 GCR 中運行:

docker push gcr.io/PROJECT_ID/crystal-www-example:latest

配置 Kubernetes

對於此示例,我們將創建幾個 yaml 文件來表示各種服務,然後通過運行 kubectl apply 在集群中配置它們。Kubernetes 的配置具有描述性,這些 yaml 文件將會告訴 Kubernetes 我們希望看到的狀態。

我們需要做的事情:

  • 為我們的 crystal-www-example Web 應用程序創建部署和服務;
  • 為 Nginx 創建一個 Daemon Set 和 Config Map;
  • 運行自定義應用程序使用 Cloudflare 到 DNS 同步 node IP。

Web App 配置

首先讓我們配置 webapp:(先將 PROJECT_ID 替換為你的項目 ID)

# kubernetes-config/crystal-www-example.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: crystal-www-example
labels:
app: crystal-www-example
spec:
replicas: 1
selector:
matchLabels:
app: crystal-www-example
template:
metadata:
labels:
app: crystal-www-example
spec:
containers:
- name: crystal-www-example
image: gcr.io/PROJECT_ID/crystal-www-example:latest
ports:
- containerPort: 8080---

kind: Service
apiVersion: v1
metadata:
name: crystal-www-example
spec:
selector:
app: crystal-www-example
ports:
- protocol: TCP
port: 8080
targetPort: 8080

現在創建一個 Deployment,它會通知 Kubernetes 創建一個 Pod,其中包含一個運行 Docker 容器的容器,以及一個用於集群內的 service discovery。要應用此配置運行(在 kubernetes-config 文件夾中):

kubectl apply -f .

我們可以使用以下方法測試它是否在運行:

kubectl get pod
# you should see something like:
# crystal-www-example-698bbb44c5-l9hj9 1/1 Running 0 5m

我們還可以創建代理 API,以便我們訪問它:

kubectl proxy

然後訪問:

http://localhost:8001/api/v1/namespaces/default/services/crystal-www-example/proxy/

Nginx 配置

通常,在 Kubernetes 中處理 HTTP 服務時,你會使用 ingress controller。不幸的是,Google 的 HTTP 負載均衡器非常昂貴,因此我們將運行自己的 HTTP 代理並手動配置它。

我們將使用 Daemon Set 和 Config Map。Daemon Set 是在每個節點上運行的應用程序。Config Map 基本上是一個小文件,我們可以在容器中安裝它,我們將存儲 Nginx 配置。

yaml 看起來像這樣:

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nginx
labels:
app: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
containers:
- image: nginx:1.15.3-alpine
name: nginx
ports:
- name: http
containerPort: 80
hostPort: 80
volumeMounts:
- name: "config"
mountPath: "/etc/nginx"
volumes:
- name: config
configMap:
name: nginx-conf

---

apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-conf
data:
nginx.conf: |
worker_processes 1;
error_log /dev/stdout info;

events {
worker_connections 10;
}

http {
access_log /dev/stdout;

server {
listen 80;
location / {
proxy_pass http://crystal-www-example.default.svc.cluster.local:8080;
}
}
}

你可以看到我們如何在 Nginx 容器內掛載 config map 的 nginx.conf。我們還在規範上設置了兩個附加欄位:hostNetwork: true、dnsPolicy: ClusterFirstWithHostNet。

  • hostNetwork: true 以便我們可以綁定主機埠並從外網到達 Nginx;
  • dnsPolicy: ClusterFirstWithHostNet 以便我們可以訪問集群內的服務。

應用更改:通過點擊節點的公共 IP 來到達 Nginx。

你可以通過運行找到:

kubectl get node -o yaml
# look for:
# - address: ...
# type: ExternalIP

我們的網路應用程序現在可通過互聯網訪問了。現在想想給它起一個好聽的名字!

連接 DNS

我們需要 A 為集群的節點設置 3 條 DNS 記錄:

然後添加一個 CNAME 條目以指向那些 A 記錄。(即 http://www.example.com CNAME http://kubernetes.example.com)我們可以手動執行此操作,但最好自動執行此操作,以便在擴展或替換節點時 DNS 記錄自動更新。

我認為這也是一個很好的說明示例,說明如何讓 Kubernetes 為你工作而不是反對它。Kubernetes 完全可編寫腳本,並且具有強大的 API。因此你可以使用不太難編寫的自定義組件填補空白。我為此構建了一個小型 Go 應用程序,可在此處找到:kubernetes-cloudflare-sync。

建立一個 informer:

factory := informers.NewSharedInformerFactory(client, time.Minute)
lister := factory.Core().V1().Nodes().Lister()
informer := factory.Core().V1().Nodes().Informer()
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
resync()
},
UpdateFunc: func(oldObj, newObj interface{}) {
resync()
},
DeleteFunc: func(obj interface{}) {
resync()
},
})
informer.Run(stop)

informer 將在節點更改時隨時調用我的 resync 函數。然後使用 Cloudflare API 庫(github.com/cloudflare/c)同步 IP ,類似於:

var ips []stringfor _, node := range nodes { for _, addr := range node.Status.Addresses { if addr.Type == core_v1.NodeExternalIP {
ips = append(ips, addr.Address)
}
}
}
sort.Strings(ips)for _, ip := range ips {
api.CreateDNSRecord(zoneID, cloudflare.DNSRecord{
Type: "A",
Name: options.DNSName,
Content: ip,
TTL: 120,
Proxied: false,
})
}

就像我們的網路應用程序一樣,我們將此應用程序作為 Kubernetes 中的部署:

apiVersion: apps/v1
kind: Deployment
metadata:
name: kubernetes-cloudflare-sync
labels:
app: kubernetes-cloudflare-sync
spec:
replicas: 1
selector:
matchLabels:
app: kubernetes-cloudflare-sync
template:
metadata:
labels:
app: kubernetes-cloudflare-sync
spec:
serviceAccountName: kubernetes-cloudflare-sync
containers:
- name: kubernetes-cloudflare-sync
image: gcr.io/PROJECT_ID/kubernetes-cloudflare-sync
args:
- --dns-name=kubernetes.example.com
env:
- name: CF_API_KEY
valueFrom:
secretKeyRef:
name: cloudflare
key: api-key
- name: CF_API_EMAIL
valueFrom:
secretKeyRef:
name: cloudflare
key: email

你需要使用 cloudflare api 密鑰和電子郵件地址創建 Kubernetes 密碼:

kubectl create secret generic cloudflare --from-literal=email=EMAIL --from-literal=api-key=API_KEY

你還需要創建服務帳戶(它允許我們的部署訪問 Kubernetes API 來檢索節點)。首次運行(特別是對於 GKE):

kubectl create clusterrolebinding cluster-admin-binding --clusterrole cluster-admin --user YOUR_EMAIL_ADDRESS_HERE

然後申請:

apiVersion: v1
kind: ServiceAccount
metadata:
name: kubernetes-cloudflare-sync
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kubernetes-cloudflare-sync
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kubernetes-cloudflare-sync-viewer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kubernetes-cloudflare-sync
subjects:
- kind: ServiceAccount
name: kubernetes-cloudflare-sync
namespace: default

配置就緒後,運行 Cloudflare 的應用程序將在任何節點更改時被更新。以上就是基於 Kubernetes 部署小型集群的詳細教程。

總 結

Kubernetes 就是這樣一個收放自如的技術。就像你可能永遠用不到 SQL 資料庫中的所有功能,但你不得不承認 SQL 資料庫極大地提高了你快速交付解決方案的能力。

Kubernetes 與 SQL 十分相似。在 Kubernetes 龐大的技術體系下,我們也並不能用到所有功能,卻能在每個項目中恰到好處的使用部分功能實現完美部署。在每次利用 Kubernetes 部署小型集群時,我都會從中獲得新的認知。

所以我的觀點是,Kubernetes 對於小型部署也很有意義,而且既易於使用又便宜。如果你從來沒有嘗試過,現在就開動起來吧!

u.wechat.com/MOSBHys0PK (二維碼自動識別)


推薦閱讀:
相关文章