最小可行的 Kubernetes

文章的翻譯是在課程開始前夕準備的 “DevOps 實踐和工具”.

最小可行的 Kubernetes

如果您正在閱讀本文,您可能聽說過一些有關 Kubernetes 的事情(如果沒有,您是怎麼來到這裡的?)但是 Kubernetes 到底是什麼? 這 “工業級容器的編排”? 或者 《雲端原生作業系統》? 這究竟意味著什麼?

老實說,我不是100%確定。 但我認為深入研究內部結構並了解 Kubernetes 在其多層抽像下到底發生了什麼是很有趣的。 因此,為了好玩,讓我們來看看最小的「Kubernetes 叢集」實際上是什麼樣子。 (這會比 Kubernetes 的艱難之路.)

我假設您具備 Kubernetes、Linux 和容器的基本知識。 我們在這裡討論的所有內容僅用於研究/學習目的,請勿將其投入生產!

Обзор

Kubernetes 包含許多元件。 根據 維基百科,架構如下圖所示:

最小可行的 Kubernetes

這裡至少顯示了八個組件,但我們將忽略其中的大多數。 我想說的是,可以合理地稱為 Kubernetes 的最低限度由三個主要組件組成:

  • 庫貝萊
  • kube-apiserver(依賴 etcd - 它的資料庫)
  • 容器運行時(在本例中為 Docker)

讓我們看看文件中關於它們的內容(羅斯., 英語.)。 首先 庫貝萊:

叢集中每個節點上運行的代理程式。 它確保容器在 Pod 中運作。

聽起來很簡單。 關於什麼 容器運作時 (容器運作時)?

容器運行時是設計用於運行容器的程式。

資訊非常豐富。 但如果你熟悉 Docker,那麼你應該對它的作用有一個大致的了解。 (容器運行時和 kubelet 之間職責分離的細節實際上非常微妙,我在這裡不再贅述。)

И API伺服器?

API Server 是公開 Kubernetes API 的 Kubernetes 控制台元件。 API伺服器是Kubernetes控制台的客戶端

任何使用過 Kubernetes 的人都必須直接或透過 kubectl 與 API 進行互動。 這是 Kubernetes 的核心——它將我們所熟知和喜愛的 YAML 大山(?)轉變為工作基礎設施的大腦。 很明顯,API 應該會出現在我們的最小配置中。

先決條件

  • 具有 root 存取權限的 Linux 虛擬機器或實體機器(我在虛擬機器上使用 Ubuntu 18.04)。
  • 這就是全部!

鑽孔安裝

我們需要在我們將使用的機器上安裝 Docker。 (我不會詳細介紹 Docker 和容器的工作原理;如果您有興趣,可以查看 精彩文章)。 讓我們安裝它 apt:

$ sudo apt install docker.io
$ sudo systemctl start docker

之後,我們需要取得 Kubernetes 二進位。 事實上,對於我們的「叢集」的初始啟動,我們只需要 kubelet,因為要運行其他伺服器元件,我們可以使用 kubelet。 為了在集群運行後與集群進行交互,我們還將使用 kubectl.

$ curl -L https://dl.k8s.io/v1.18.5/kubernetes-server-linux-amd64.tar.gz > server.tar.gz
$ tar xzvf server.tar.gz
$ cp kubernetes/server/bin/kubelet .
$ cp kubernetes/server/bin/kubectl .
$ ./kubelet --version
Kubernetes v1.18.5

如果我們只是跑會發生什麼 kubelet?

$ ./kubelet
F0609 04:03:29.105194    4583 server.go:254] mkdir /var/lib/kubelet: permission denied

kubelet 必須以 root 身分運行。 非常合乎邏輯,因為他需要管理整個節點。 我們來看看它的參數:

$ ./kubelet -h
<слишком много строк, чтобы разместить здесь>
$ ./kubelet -h | wc -l
284

哇,這麼多選擇! 幸運的是,我們只需要其中幾個。 這是我們感興趣的參數之一:

--pod-manifest-path string

包含靜態 pod 檔案的目錄路徑,或描述靜態 pod 的檔案的路徑。 以點開頭的文件將被忽略。 (已棄用:必須在透過 --config 選項傳遞到 Kubelet 的設定檔中設定此選項。有關更多信息,請參閱 kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)

該選項允許我們運行 靜態吊艙 — 不透過 Kubernetes API 管理的 Pod。 靜態 Pod 很少使用,但是它們非常方便快速建立集群,而這正是我們所需要的。 我們將忽略這個大警告(再次強調,不要在生產中運行它!)並看看我們是否可以讓 Pod 運行。

首先我們將為靜態 Pod 建立目錄並執行 kubelet:

$ mkdir pods
$ sudo ./kubelet --pod-manifest-path=pods

然後,在另一個終端機/tmux 視窗/其他視窗中,我們將建立一個 pod 清單:

$ cat <<EOF > pods/hello.yaml
apiVersion: v1
kind: Pod
metadata:
  name: hello
spec:
  containers:
  - image: busybox
    name: hello
    command: ["echo", "hello world!"]
EOF

kubelet 開始寫一些警告,但似乎什麼事也沒發生。 但事實並非如此! 我們來看看 Docker:

$ sudo docker ps -a
CONTAINER ID        IMAGE                  COMMAND                 CREATED             STATUS                      PORTS               NAMES
8c8a35e26663        busybox                "echo 'hello world!'"   36 seconds ago      Exited (0) 36 seconds ago                       k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
68f670c3c85f        k8s.gcr.io/pause:3.2   "/pause"                2 minutes ago       Up 2 minutes                                    k8s_POD_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_0
$ sudo docker logs k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
hello world!

kubelet 我閱讀了 pod 清單,並向 Docker 發出了根據我們的規格啟動幾個容器的命令。 (如果您想知道「暫停」容器,這是 Kubernetes hack - 請參閱 這個部落格.) Kubelet 將啟動我們的容器 busybox 使用指定的命令,並將無限期地重新啟動它,直到靜態 Pod 被刪除。

祝賀你自己。 我們剛剛想出了一種將文字輸出到終端的最令人困惑的方法!

啟動etcd

我們的最終目標是運行 Kubernetes API,但要做到這一點,我們首先需要運行 。 讓我們透過將其設定放在 pods 目錄中來啟動一個最小的 etcd 叢集(例如, pods/etcd.yaml):

apiVersion: v1
kind: Pod
metadata:
  name: etcd
  namespace: kube-system
spec:
  containers:
  - name: etcd
    command:
    - etcd
    - --data-dir=/var/lib/etcd
    image: k8s.gcr.io/etcd:3.4.3-0
    volumeMounts:
    - mountPath: /var/lib/etcd
      name: etcd-data
  hostNetwork: true
  volumes:
  - hostPath:
      path: /var/lib/etcd
      type: DirectoryOrCreate
    name: etcd-data

如果您曾經使用過 Kubernetes,那麼您應該熟悉這些 YAML 檔案。 這裡只有兩點值得注意:

我們已經掛載了host資料夾 /var/lib/etcd 以便在重新啟動後保留 etcd 資料(如果不這樣做,每次重新啟動 pod 時叢集狀態都會被刪除,這對於最小的 Kubernetes 安裝也不是好事)。

我們已經安裝了 hostNetwork: true。 不出所料,此設定將 etcd 配置為使用主機網路而不是 pod 的內部網路(這將使 API 伺服器更容易找到 etcd 叢集)。

一個簡單的檢查顯示 etcd 確實在本機上運行並將資料保存到磁碟:

$ curl localhost:2379/version
{"etcdserver":"3.4.3","etcdcluster":"3.4.0"}
$ sudo tree /var/lib/etcd/
/var/lib/etcd/
└── member
    ├── snap
    │   └── db
    └── wal
        ├── 0.tmp
        └── 0000000000000000-0000000000000000.wal

啟動API伺服器

運行 Kubernetes API 伺服器更加容易。 唯一需要傳遞的參數是 --etcd-servers,執行您期望的操作:

apiVersion: v1
kind: Pod
metadata:
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - name: kube-apiserver
    command:
    - kube-apiserver
    - --etcd-servers=http://127.0.0.1:2379
    image: k8s.gcr.io/kube-apiserver:v1.18.5
  hostNetwork: true

將此 YAML 檔案放入目錄中 pods,API 伺服器將啟動。 檢查與 curl 顯示 Kubernetes API 正在偵聽連接埠 8080,並且完全開放存取 - 無需身份驗證!

$ curl localhost:8080/healthz
ok
$ curl localhost:8080/api/v1/pods
{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {
    "selfLink": "/api/v1/pods",
    "resourceVersion": "59"
  },
  "items": []
}

(再次強調,不要在生產中運行它!我對預設設定如此不安全感到有點驚訝。但我猜這是為了使開發和測試更容易。)

而且,令人驚喜的是,kubectl 開箱即用,無需任何額外設定!

$ ./kubectl version
Client Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.5", GitCommit:"e6503f8d8f769ace2f338794c914a96fc335df0f", GitTreeState:"clean", BuildDate:"2020-06-26T03:47:41Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.5", GitCommit:"e6503f8d8f769ace2f338794c914a96fc335df0f", GitTreeState:"clean", BuildDate:"2020-06-26T03:39:24Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
$ ./kubectl get pod
No resources found in default namespace.

問題

但再深入一點,你會發現有些地方出了問題:

$ ./kubectl get pod -n kube-system
No resources found in kube-system namespace.

我們創建的靜態 Pod 消失了! 事實上,我們的 kubelet 節點根本沒有被發現:

$ ./kubectl get nodes
No resources found in default namespace.

怎麼了? 如果您還記得前幾段,我們使用一組極其簡單的命令列參數啟動了 kubelet,因此 kubelet 不知道如何聯絡 API 伺服器並通知其狀態。 經過研究文檔,我們找到了對應的flag:

--kubeconfig string

文件的路徑 kubeconfig,它指定如何連接到 API 伺服器。 可用性 --kubeconfig 啟用 API 伺服器模式,否 --kubeconfig 啟用離線模式。

一直以來,我們都在不知不覺中以「離線模式」運行 kubelet。 (如果我們是迂腐的,我們可以將獨立的 kubelet 視為“最小可行的 Kubernetes”,但這會很無聊)。 為了讓「真正的」配置運作,我們需要將 kubeconfig 檔案傳遞給 kubelet,以便它知道如何與 API 伺服器通訊。 幸運的是,這非常簡單(因為我們沒有任何身份驗證或證書問題):

apiVersion: v1
kind: Config
clusters:
- cluster:
    server: http://127.0.0.1:8080
  name: mink8s
contexts:
- context:
    cluster: mink8s
  name: mink8s
current-context: mink8s

將此另存為 kubeconfig.yaml,殺死進程 kubelet 並使用必要的參數重新啟動:

$ sudo ./kubelet --pod-manifest-path=pods --kubeconfig=kubeconfig.yaml

(順便說一句,如果你在 kubelet 沒有運行時嘗試通過curl 訪問 API,你會發現它仍在運行!Kubelet 並不像 Docker 那樣是其 pod 的“父級”,它更像是一個“控件”守護進程。」由kubelet 管理的容器將繼續運行,直到kubelet 停止它們。)

幾分鐘後 kubectl 應該向我們展示我們期望的 pod 和節點:

$ ./kubectl get pods -A
NAMESPACE     NAME                    READY   STATUS             RESTARTS   AGE
default       hello-mink8s            0/1     CrashLoopBackOff   261        21h
kube-system   etcd-mink8s             1/1     Running            0          21h
kube-system   kube-apiserver-mink8s   1/1     Running            0          21h
$ ./kubectl get nodes -owide
NAME     STATUS   ROLES    AGE   VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION       CONTAINER-RUNTIME
mink8s   Ready    <none>   21h   v1.18.5   10.70.10.228   <none>        Ubuntu 18.04.4 LTS   4.15.0-109-generic   docker://19.3.6

這次讓我們真正祝賀自己(我知道我已經祝賀自己了)——我們有一個最小的 Kubernetes“集群”,運行著一個功能齊全的 API!

我們在下面啟動

現在讓我們來看看 API 的功能。 讓我們從 nginx pod 開始:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx

這裡我們得到一個相當有趣的錯誤:

$ ./kubectl apply -f nginx.yaml
Error from server (Forbidden): error when creating "nginx.yaml": pods "nginx" is
forbidden: error looking up service account default/default: serviceaccount
"default" not found
$ ./kubectl get serviceaccounts
No resources found in default namespace.

在這裡我們可以看到我們的 Kubernetes 環境是多麼不完整——我們沒有服務帳戶。 讓我們再次嘗試手動建立服務帳戶,看看會發生什麼:

$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
EOS
serviceaccount/default created
$ ./kubectl apply -f nginx.yaml
Error from server (ServerTimeout): error when creating "nginx.yaml": No API
token found for service account "default", retry after the token is
automatically created and added to the service account

即使我們手動建立服務帳戶,也不會產生身份驗證令牌。 當我們繼續嘗試我們的簡約「集群」時,我們會發現通常自動發生的大多數有用的事情都會丟失。 Kubernetes API 伺服器非常簡約,大部分繁重的工作和自動設定都發生在尚未執行的各種控制器和背景作業中。

我們可以透過設定選項來解決這個問題 automountServiceAccountToken 對於服務帳戶(因為我們無論如何都不必使用它):

$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
automountServiceAccountToken: false
EOS
serviceaccount/default configured
$ ./kubectl apply -f nginx.yaml
pod/nginx created
$ ./kubectl get pods
NAME    READY   STATUS    RESTARTS   AGE
nginx   0/1     Pending   0          13m

終於,豆莢出現了! 但實際上它不會啟動,因為我們沒有 規劃師 (調度器)是 Kubernetes 的另一個重要元件。 我們再次看到 Kubernetes API 出乎意料地「愚蠢」——當您在 API 中創建 Pod 時,它會註冊它,但不會嘗試找出在哪個節點上運行它。

事實上,您不需要排程器來執行 Pod。 您可以在參數中手動將節點新增至清單中 nodeName:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
  nodeName: mink8s

(代替 mink8s 到節點的名稱。)刪除並套用後,我們看到nginx已經啟動並且正在監聽內部IP位址:

$ ./kubectl delete pod nginx
pod "nginx" deleted
$ ./kubectl apply -f nginx.yaml
pod/nginx created
$ ./kubectl get pods -owide
NAME    READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          30s   172.17.0.2   mink8s   <none>           <none>
$ curl -s 172.17.0.2 | head -4
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

為了確保 Pod 之間的網路正常運作,我們可以從另一個 Pod 運行curl:

$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: curl
spec:
  containers:
  - image: curlimages/curl
    name: curl
    command: ["curl", "172.17.0.2"]
  nodeName: mink8s
EOS
pod/curl created
$ ./kubectl logs curl | head -6
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

深入研究這個環境並看看什麼有效、什麼無效是非常有趣的。 我發現 ConfigMap 和 Secret 按預期工作,但 Service 和 Deployment 沒有。

成功!

這篇文章越來越長,所以我要宣布勝利,並說這是一個可以稱為「Kubernetes」的可行配置。總結一下:四個二進位檔案、五個命令列參數和「僅」45 行YAML(不是以 Kubernetes 的標準來說就這麼多了),我們有很多東西在工作:

  • Pod 使用常規 Kubernetes API 進行管理(需要一些技巧)
  • 您可以上傳和管理公共容器映像
  • Pod 保持活動狀態並自動重新啟動
  • 同一節點內的 Pod 之間的網路運作良好
  • ConfigMap、Secret 和簡單儲存安裝如預期般運作

但 Kubernetes 真正有用的許多東西仍然缺失,例如:

  • Pod 調度程式
  • 認證/授權
  • 多個節點
  • 服務網路
  • 叢集內部 DNS
  • 用於服務帳戶、部署、與雲端供應商整合以及 Kubernetes 帶來的大多數其他好處的控制器

那麼我們實際上得到了什麼? 獨立運作的 Kubernetes API 其實只是一個平台 貨櫃自動化。 它的作用並不大——這是使用 API 的各種控制器和操作員的工作——但它確實為自動化提供了一致的環境。

在免費網路研討會中了解有關該課程的更多資訊。

閱讀更多:

來源: www.habr.com

添加評論