隨著 Kubernetes 逐步成為容器 orchestrator 領域的事實標準,在 Kubernetes 生態領域周邊有例如 Cluster API、Cloud Provider 等很多項目。本文介紹了 Cluster API 這個項目在 OpenStack 上的實現;同時,本文也介紹了 CoreOS 這個容器化操作系統如何在 Cluster API 上使用。
Cluster API 以及 cluster-api-provider-openstack 簡介
Cluster API 是一個 Kubernetes 之上的可選項目,它使用 Kuberentes 原生方式來創建、配置和管理集群(Cluster)。本文假設讀者已經對 Cluster API 有一定了解,關於 Cluster API 使用、範圍等內容可以參考 此處 ;關於 Cluster API 的基本概念和操作可以參考這篇文章。
Cluster API 作為通用的 Kubernetes 集群管理框架,許多不同種類的雲提供商都以 Cluster API 為基礎,您可從 這裡 獲取雲提供商的完整列表。其中基於 OpenStack 的實現為 cluster-api-provider-openstack,為了簡單起見,本文使用縮寫 CAPO 代表 cluster-api-provider-openstack。
CoreOS 簡介
CoreOS 是一個基於 Linux 內核的輕量級操作系統,面向雲和集群為主,其主要的優勢是一致、安全、可靠。關於 CoreOS 具體可以參考 官方文檔 。
CoreOS 作為雲原生操作系統,ignition 是其很有特色以及和普通 Linux 不一樣的地方。下面這個 ignition 實例的主要目的就是將生成的公鑰注入到部署的虛擬機裡,這樣當 CoreOS 啟動之後,可以通過公鑰對應的私鑰 ssh 登錄。
<code>{ "ignition": { "config": {}, "timeouts": {}, "version": "3.0.0" }, "networkd": {}, "passwd": { "users": [ { "name": "core", "sshAuthorizedKeys": [ "ssh-rsa ABCD..." ] } ] }, "storage": {}, "systemd": { }/<code>
OpenStack 的 ingition 可以參考該 文檔 。CAPO 使用了 ignition 來初始化 CoreOS。
CoreOS 在 CAPO 中的使用
在 CAPO 中默認主要通過如下幾個配置文件:cluster.yaml 中主要定義集群,例如集群的網絡、master IP 地址等;machines.yaml 定義所有的機器,例如規格、網絡、安全組等;provider-components.yaml 定義 role、secret 等,其中最重要的就是 worker-user-data 和 master-user-data 兩個 secret。
<code>apiVersion: "cluster.k8s.io/v1alpha1" kind: Cluster metadata: name: test1 spec: clusterNetwork: services: cidrBlocks: ["10.96.0.0/12"] pods: cidrBlocks: ["192.168.0.0/16"] serviceDomain: "cluster.local" providerSpec: value: apiVersion: "openstackproviderconfig/v1alpha1" kind: "OpenstackProviderSpec" clusterConfiguration: controlPlaneEndpoint: 9.20.206.208:6443 kubernetesVersion: 1.15.0/<code>
CAPO 邏輯上可以拆分成為 infra(也就是虛擬機和其他諸如存儲、負載均衡等相關資源)和 Kubernetes 控制和計算節點創建; 當前 Kubernetes 的創建是通過注入啟動腳本來實現的,CAPO 現在支持 Ubuntu、CentOS、CoreOS,Ubuntu 和 CentOS 使用的是 cloud-init,CoreOS 使用的是 ignition。而 cloud-init 或者 ignition 的內容就是通過上述 worker-user-data 和 master-user-data 來完成的。
如下是針對 CoreOS 和非 CoreOS 創建的區別:
<code>if [[ "$PROVIDER_OS" == "coreos" ]]; then cat $COREOS_COMMON_SECTION \ | sed -e "s#\$OPENSTACK_CLOUD_PROVIDER_CONF#$OPENSTACK_CLOUD_PROVIDER_CONF#" \ | sed -e "s#\$OPENSTACK_CLOUD_CACERT_CONFIG#$OPENSTACK_CLOUD_CACERT_CONFIG#" \ | yq m -a - $COREOS_MASTER_SECTION \ > $COREOS_MASTER_USER_DATA cat $COREOS_COMMON_SECTION \ | sed -e "s#\$OPENSTACK_CLOUD_PROVIDER_CONF#$OPENSTACK_CLOUD_PROVIDER_CONF#" \ | sed -e "s#\$OPENSTACK_CLOUD_CACERT_CONFIG#$OPENSTACK_CLOUD_CACERT_CONFIG#" \ | yq m -a - $COREOS_WORKER_SECTION \ > $COREOS_WORKER_USER_DATA else cat "$MASTER_USER_DATA" \ | sed -e "s#\$OPENSTACK_CLOUD_PROVIDER_CONF#$OPENSTACK_CLOUD_PROVIDER_CONF#" \ | sed -e "s#\$OPENSTACK_CLOUD_CACERT_CONFIG#$OPENSTACK_CLOUD_CACERT_CONFIG#" \ > $USERDATA/$PROVIDER_OS/master-user-data.sh cat "$WORKER_USER_DATA" \ | sed -e "s#\$OPENSTACK_CLOUD_PROVIDER_CONF#$OPENSTACK_CLOUD_PROVIDER_CONF#" \ | sed -e "s#\$OPENSTACK_CLOUD_CACERT_CONFIG#$OPENSTACK_CLOUD_CACERT_CONFIG#" \ > $USERDATA/$PROVIDER_OS/worker-user-data.sh Fi/<code>
通過如下命令,可以通過預先定義的模板生成最終的 ignition 文件:
<code>./generate-yaml.sh clouds.yaml openstack coreos output/<code>
當控制節點啟動的時候,ignition 會由 systemd 觸發執行,從而會完成一系列初始化動作,例如創建臨時目錄,創建需要的配置文件,然後執行其中最核心的 kubeadm 啟動腳本完成 Kubernetes 集群的創建。
當控制節點完成 Kubernetes 集群的創建之後,所有的之前在臨時集群的 Kubernetes 的對象都會被轉移到新創建的控制集群裡,之後開始創建工作節點,同控制節點一樣,工作節點也會執行 ingition,工作節點的 ignition 和控制節點的不同,主要是通過 kubeadm join 來加入控制節點。
創建 CNI 和 CRICTL:
<code>systemd: units: … - contents: | [Unit] Description=Unpack CNI and CRICTL into the right places. Before=kubelet.service [Service] Type=oneshot ExecStartPre=/usr/bin/tar -C /opt/bin -xzf /opt/kubetmp/crictl.tar.gz ExecStart=/usr/bin/tar -C /opt/cni/bin -xzf /opt/kubetmp/cni-plugins.tar.gz ExecStartPost=/usr/bin/systemctl disable unpack.service [Install] WantedBy=multi-user.target enabled: true name: unpack.service 創建 Kubernetes 控制面: systemd: units: …. - contents: |- [Unit] Description=Initialise Kubernetes control plane node. After=kubelet.service Requires=coreos-metadata.service [Service] Type=oneshot Environment="PATH=/usr/bin:/usr/sbin:/opt/bin:/opt/cni/bin:/bin/sbin" ExecStartPre=/opt/bin/prepare.sh ExecStart=/opt/bin/kubeadm init --config /etc/kubernetes/kubeadm_config.yaml ExecStartPost=/opt/bin/kubectl --kubeconfig /etc/kubernetes/kubelet.conf annotate --overwrite node %H machine={{ .Machine.ObjectMeta.Namespace }}/{{ .Machine.ObjectMeta.Name }} ExecStartPost=/opt/bin/kubectl --kubeconfig /etc/kubernetes/admin.conf apply -f https://docs.projectcalico.org/v3.6/getting-started/kubernetes/installation/hosted/kubernetes-datastore/calico-networking/1.7/calico.yaml ExecStartPost=/usr/bin/systemctl disable kubeadm.service [Install] WantedBy=multi-user.target enabled: true name: kubeadm.service/<code>
生成 ignition 模板可以參考 這裡 。
多控制節點在 CAPO 中的使用
Kubernetes 通過多個控制節點來支持高可用,kubeadm 這個項目已經支持了高可用的設置,CAPO 利用了 kubeadm 的這個功能,在控制節點啟動時候決定是創建還是加入 Kubernetes 控制節點。
Opentsack 通過 octavia 項目支持負載均衡,octavia 是 OpenStack 的一個子項目,CAPO 通過 gophercloud 這個項目的 golang 接口來創建和管理負載均衡器。Octavia 的具體使用辦法可以參考 這裡 。
圖 1. 帶負載均衡器的 Kubernetes 集群
CAPO 通過大致如下步驟來完成多控制節點集群創建,具體的細節本文篇幅有限,不能詳細介紹。
- clusterctl 使用 kind 或者 minikube 創建一個臨時集群,創建臨時集群的目的是創建一個 Kubernetes 環境,可以把 Kubernetes 定義的對象從 yaml 定義加入到 etcd 中,這個臨時集群最終會被刪除或保留。
- 臨時集群 CAPO 的容器的集群控制器(Cluster Controller)觀察到需要創建負載均衡器,會通過調用 octavia 創建一個負載均衡器,這個負載均衡器會創建監聽對象等。
- clusterctl 根據 machines 的定義創建多個虛擬機共同組成控制面,如前所示,如果採用 CoreOS,會使用 CoreOS 的鏡像以及執行 CoreOS 的啟動腳本 (ignition)。
- 第一個虛擬機通過 kubeadm 創建集群控制面,當完成之後 CAPO 容器的機器控制器 (machine Controller) 會把該虛擬機加入之前創建的負載均衡器成為它的第一個成員。
- 此後,其他創建的虛擬機加入之前的控制面並加入負載均衡器成為其成員。
- 控制權從臨時集群交給創建的集群,主要的工作是把所有在臨時集群的定義通過 pivot 這個方法在新集群的 etcd 里加入 machines、cluster 等定義。
- 根據用戶的 Machines 定義創建工作節點,並作為 node 加入之前創建的新集群。
- 根據用戶的選擇(默認是刪除)刪除臨時集群。
- 從此用戶可以通過負載均衡器的 ip 和端口來訪問新的集群。
CAPO 加入負載均衡器的配置如下:
managedAPIServerLoadBalancer 代表創建負載均衡器與否,只有選擇 true 才會創建,否則就會創建一個單機的沒有高可用的 Kubernetes 控制面。
useOctavia 代表使用 Octavia 與否,openstack neutron lbaas 也支持負載均衡器,但 Octavia 提供更強大的功能,並且很快 neutron lbaas 也會被刪除,所以推薦使用 Octavia。
APIServerLoadBalancerFloatingIP 代表負載均衡器的 IP 地址。
<code>apiVersion: "cluster.k8s.io/v1alpha1" kind: Cluster metadata: name: test1 spec: …… providerSpec: value: …… managedAPIServerLoadBalancer: true useOctavia: true APIServerLoadBalancerFloatingIP: 1.2.3.4 clusterConfiguration: controlPlaneEndpoint: 1.2.3.4:6443 kubernetesVersion: 1.15.0/<code>
Cluster API 的演進和開發
Cluster API 的第一版 v1alpha1 有一些技術性的問題, 這篇文檔 有很詳細的介紹,總結如下:
- 單個 provider 提供基礎架構也提供 Kubernetes 會帶來很多限制,v1alpha1 中如果在一個提供商上增加新的 Kubernetes 的支持類如 Rancher、Openshift 等需要提供商提供支持。
- 每一個 Provider 都提供自己的配置和狀態,這個是通過 RAW 形式存在的因此沒有辦法驗證參數的有效性。
- 在同一個 Cluster 中不能支持不同的 provider,不能支持異構雲。
通過 v1alpha2 的演進能夠做到:
- 通過 Kubernetes 控制器管理(虛擬)機
- Bootstrap 實現可以在 provider 之間共享
- 支持 cloud-init、ignition 等不同的啟動引擎
- 在同一個 provider 上支持不同的 Kubernetes 發行版,例如原生 Kubernetes、openshift 等
- 儘早的驗證 provider 特定的內容符合 API 格式
CAPO 的 master 分支 正在快速跟進 Cluster API 的 v1alpha2 開發中。
結束語
本文介紹了 CoreOS 和 CAPO 的基本概念以及在 CAPO 中使用 CoreOS 的方法,以及如何在 CAPO 中使用負載均衡器創建和管理多個控制節點,最後介紹了 Cluster-API 從 v1alpha1 到 v1alpha2 的演進。