Kubernetes入門教程

前言:本文是一篇 kubernetes(下文用 k8s 代替)的入門文章,將會涉及 k8s 的架構、叢集搭建、一個 Redis 的例子,以及如何使用 operator-sdk 開發 operator 的教程。在文章過程中,會穿插引出 Pod、Deployment、StatefulSet 等 k8s 的概念,這些概念透過例子引出來,更容易理解和實踐。文章參考了很多部落格以及資料,放在最後參考資料部分。

一  k8s架構

我們看下 k8s 叢集的架構,從左到右,分為兩部分,第一部分是 Master 節點(也就是圖中的 Control Plane),第二部分是 Node 節點。
Master 節點一般包括四個元件,apiserver、scheduler、controller-manager、etcd,他們分別的作用是什麼:
  • Apiserver:上知天文下知地理,上連其餘元件,下接ETCD,提供各類 api 處理、鑑權,和 Node 上的 kubelet 通訊等,只有 apiserver 會連線 ETCD。
  • Controller-manager:控制各類 controller,透過控制器模式,致力於將當前狀態轉變為期望的狀態。
  • Scheduler:排程,打分,分配資源。
  • Etcd:整個叢集的資料庫,也可以不部署在 Master 節點,單獨搭建。
Node 節點一般也包括三個元件,docker,kube-proxy,kubelet
  • Docker:具體跑應用的載體。
  • Kube-proxy:主要負責網路的打通,早期利用 iptables,現在使用 ipvs技術。
  • Kubelet:agent,負責管理容器的生命週期。
總結一下就是 k8s 叢集是一個由兩部分元件 Master 和 Node 節點組成的架構,其中 Master 節點是整個叢集的大腦,Node 節點來執行 Master 節點排程的應用,我們後續會以一個具體的排程例子來解釋這些元件的互動過程。

二  搭建 k8s 叢集

上面說完了 k8s 叢集中有哪些元件,接下來我們先看下如何搭建一個 k8s 叢集,有以下幾種方法(參考文末連結)
  • 當我們安裝了 Docker Desktop APP 之後,勾選 k8s 支援就能搭建起來。
  • 使用 MiniKube 來搭建,社群提供的一鍵安裝指令碼。
  • 直接在雲平臺購買,例如阿里雲 ack。
  • 使用 kubeadmin,這是 k8s 社群推薦的可以部署生產級別 k8s 的工具。
  • 使用二進位制,下載各元件安裝,此教程需要注意,下載的各元件版本要和部落格中保持一致,就可以成功。
本文後面的例子均採用本地 Docker Desktop APP 搭建的 k8s。
➜ ~ kubectl versionClient Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.4", GitCommit:"3cce4a82b44f032d0cd1a1790e6d2f5a55d20aae", GitTreeState:"clean", BuildDate:"2021-08-11T18:16:05Z", GoVersion:"go1.16.7", Compiler:"gc", Platform:"darwin/amd64"}Server Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.4", GitCommit:"3cce4a82b44f032d0cd1a1790e6d2f5a55d20aae", GitTreeState:"clean", BuildDate:"2021-08-11T18:10:22Z", GoVersion:"go1.16.7", Compiler:"gc", Platform:"linux/amd64"}

三  從需求出發

下面我們從一個實際的需求出發,來看看如何在 k8s 上部署 Redis 服務。
  • 部署一個Redis服務
  • 支援高可用
  • 提供統一的 EndPoint 訪問地址

1  部署單機版

如果我們想在 k8s 上部署一個單機版本 Redis,我們執行下面的命令即可:
➜ ~ kubectl run redis --image=redispod/redis created➜ ~ kubectl get podsNAME READY STATUS RESTARTS AGEredis 1/1 Running 0 5s
可以用 kubectl exec 來進入到 Pod 內部連線 Redis 執行命令:
➜ ~ kubectl exec -it redis -- bashroot@redis:/data# redis-cli127.0.0.1:6379> pingPONG127.0.0.1:6379>
那麼 Pod 和 Redis 是什麼關係呢?這裡的 Redis 其實是一個 Docker 程序啟動的服務,但是在 k8s 中,它叫 Pod。

2  Pod 與 Deployment

我們來講下第一個 k8s 的概念 Pod,Pod 是 k8s 中最小的排程單元,一個 Pod 中可以包含多個 Docker,這些 Docker 都會被排程到同一臺 Node 上,這些 Docker 共享 NetWork Namespace,並且可以宣告共享同一個 Volume 來共享磁碟空間。
這樣的好處是什麼呢?其實在真實的世界中,很多應用是有部署在同一臺機器的需求的,比如 Redis 日誌採集外掛要採集日誌,肯定需要和 Redis 部署在同一臺機器上才能讀到 Redis 的日誌,我們前面講述背景的時候說到了 Docker Swarm 存在一些問題,其中之一就是它只是基於 Docker 排程,雖然也可以設定親和度讓兩臺 Docker 排程在同一個機器上,但是因為不能一起排程,所以會存在一個Docker 提前被排程到了一個資源少的機器上,從而導致第二個 Docker 排程失敗。
例如我們一共有 2 臺容器,A和B,分別為 Redis 和 日誌採集元件,各需要 2g 記憶體,現在有兩臺 node,node1 3.5 記憶體,node2 4g記憶體,在 Docker Swarm 的排程策略下,先排程 Redis,有可能被排程到了 node1 上,接下來再來排程日誌採集元件,發現 node1 只有 1.5g 記憶體了,排程失敗。但是在 k8s 中,排程是按照 pod 來排程的,兩個元件在一個 pod 中,排程就不會考慮 node1。
雖然 Pod 已經可以執行 Redis 服務了,但是他不具備高可用性,因為一旦一個 Pod 與一個節點(Node)繫結,除非這個繫結發生了變化(pod.spec.node 欄位被修改),否則它永遠都不會離開這個節點,這也就意味著,如果這個宿主機宕機了,這個 Pod 也不會主動遷移到其他節點上去。為了讓服務可以一直在,需要使用 Deployment 這樣的控制器。
➜ ~ kubectl create deployment redis-deployment --image=redisdeployment.apps/redis-deployment created➜ ~ kubectl get podsNAME READY STATUS RESTARTS AGEredis 1/1 Running 0 32mredis-deployment-866c4c6cf9-8z8k5 1/1 Running 0 8s➜ ~
redis-deployment-866c4c6cf9-8z8k5就是剛才透過 kubectl create 建立的新的 Deployment,為了驗證高可用,我們把用 kubectl delete pod 把 redisredis-deployment-866c4c6cf9-8z8k5都刪掉看會發生什麼。
➜ ~ kubectl delete pod redis redis-deployment-866c4c6cf9-8z8k5pod "redis" deletedpod "redis-deployment-866c4c6cf9-8z8k5" deleted➜ ~ kubectl get podsNAME READY STATUS RESTARTS AGEredis-deployment-866c4c6cf9-zskkb 1/1 Running 0 10s➜ ~
redis已經消失了,但是redis-deployment-866c4c6cf9-zskkb換了個名字又出現了!
Deployment 可以定義多副本個 Pod,從而為應用提供遷移能力,如果單純使用 Pod,實際上當應用被排程到某臺機器之後,機器宕機應用也無法自動遷移,但是使用 Deployment,則會呼叫 ReplicaSet(一種控制器) 來保證當前叢集中的應用副本數和指定的一致。

3  k8s 使用 yaml 來描述命令

k8s 中,可以使用 kubectl 來建立簡單的服務,但是還有一種方式是對應建立複雜的服務的,就是提供 yaml 檔案。例如上面的建立 Pod 的命令,我們可以用下面的 yaml 檔案替換,執行 kubectl create 之後,可以看到 redis Pod 又被建立了出來。
➜ ~ cat pod.yamlapiVersion: v1kind: Podmetadata: name: redisspec: containers: - name: redis image: redis➜ ~ kubectl create -f pod.yamlpod/redis created➜ ~ kubectl get podsNAME READY STATUS RESTARTS AGEredis 1/1 Running 0 6sredis-deployment-866c4c6cf9-zskkb 1/1 Running 0 6m32s

四  k8s 元件呼叫流程

下面我們看下kubectl create deployment redis-deployment –image=redis下發之後,k8s 叢集做了什麼。
  • 首先 controller-manager, scheduler, kubelet 都會和 apiserver 開始進行 List-Watch 模型,List 是拿到當前的狀態,Watch 是拿到期望狀態,然後 k8s 叢集會致力於將當前狀態達到達期望狀態。
  • kubectl 下發命令到 apiserver,鑑權處理之後將建立資訊存入 etcd,Deployment 的實現是使用 ReplicaSet 控制器,當 controller-manager 提前拿到當前的狀態(pod=0),接著接收到期望狀態,需要建立 ReplicaSet(pod=1),就會開始建立 Pod。
  • 然後 scheduler 會進行排程,確認 Pod 被建立在哪一臺 Node 上。
  • 之後 Node 上的 kubelet 真正拉起一個 docker。
這些步驟中,apiserver 的作用是不言而喻的,所以說上接其餘元件,下連 ETCD,但是 apiserver 是可以橫向擴容的,然後透過負載均衡,倒是 ETCD 在 k8s 架構中成了瓶頸。
最開始看這架構的時候,會想著為啥 apiserver, scheduler, controller-manager 不合成一個元件,其實在 Google Borg 中,borgmaster 就是這樣的,功能也是這些功能,但是合在了一起,最後他們也發現叢集大了之後 borgmaster 會有些效能上的問題,包括 kubelet 的心跳就是很大一塊,所以 k8s 從一開始開源,設計中有三個元件也是更好維護程式碼吧。

五  部署主從版本

上面我們已經部署了 Redis 的單機版,並透過 Deployment 實現了服務持續執行,接下來來看下主從版本如何部署,其中一個比較困難的地方就是如何確定主從的同步關係。

1  StatefulSet

k8s 為有狀態應用設計了 StatefulSet 這種控制器,它主要透過下面兩個特性來服務有狀態應用:
  • 拓撲狀態:例項的建立順序和編號是順序的,會按照 name-index 來編號,比如 redis-0,redis-1 等。
  • 儲存狀態:可以透過宣告使用外部儲存,例如雲盤等,將資料儲存,從而 Pod 重啟,重新排程等都能讀到雲盤中的資料。
下面我們看下 Redis 的 StatefulSet 的例子:
apiVersion: apps/v1kind: StatefulSet # 型別為 statefulsetmetadata: name: redis-sfs # app 名稱spec: serviceName: redis-sfs # 這裡的 service 下面解釋 replicas: 2 # 定義了兩個副本 selector: matchLabels: app: redis-sfs template: metadata: labels: app: redis-sfs spec: containers: - name: redis-sfs image: redis # 映象版本 command: - bash - "-c" - | set -ex ordinal=`hostname | awk -F '-' '{print $NF}'` # 使用 hostname 獲取序列 if [[ $ordinal -eq 0 ]]; then # 如果是 0,作為主 echo > /tmp/redis.conf else echo "slaveof redis-sfs-0.redis-sfs 6379" > /tmp/redis.conf # 如果是 1,作為備 fi redis-server /tmp/redis.conf
接著啟動這個 StatefulSet,發現出現了 redis-sfs-0 和 redis-sfs-1 兩個 pod,他們正式按照 name-index 的規則來編號的
➜ ~ kubectl create -f server.yamlstatefulset.apps/redis-sfs created➜ ~ kubectl get podsNAME READY STATUS RESTARTS AGEredis 1/1 Running 0 65mredis-deployment-866c4c6cf9-zskkb 1/1 Running 0 71mredis-sfs-0 1/1 Running 0 33s # 按照 redis-sfs-1 1/1 Running 0 28s
接著我們繼續看下主從關係生效了沒,檢視 redis-sfs-1 的日誌,卻發現:
➜ ~ kubectl logs -f redis-sfs-11:S 05 Nov 2021 08:02:44.243 * Connecting to MASTER redis-sfs-0.redis-sfs:63791:S 05 Nov 2021 08:02:50.287 # Unable to connect to MASTER: Resource temporarily unavailable...

2  Headless Service

似乎 redis-sfs-1 不認識 redis-sfs-0,原因就在於我們還沒有讓它們互相認識,這個互相認識需要使用 k8s 一個服務叫 Headless Service,Service 是 k8s 專案中用來將一組 Pod 暴露給外界訪問的一種機制。比如,一個 Deployment 有 3 個 Pod,那麼我就可以定義一個 Service。然後,使用者只要能訪問到這個 Service,它就能訪問到某個具體的 Pod,一般有兩種方式:
  • VIP:訪問 VIP 隨機返回一個後端的 Pod
  • DNS:透過 DNS 解析到後端某個 Pod 上
Headless Service 就是透過 DNS 的方式,可以解析到某個 Pod 的地址,這個 DNS 地址的規則就是:
<pod-name>.<svc-name>.<namespace>.svc.cluster.local
下面我們建立叢集對應的 Headless Service:
apiVersion: v1kind: Servicemetadata: name: redis-sfs labels: app: redis-sfsspec: clusterIP: None # 這裡的 None 就是 Headless 的意思,表示會主動由 k8s 分配 ports: - port: 6379 name: redis-sfs selector: app: redis-sfs
再次檢視,發現 redis-sfs-1 已經主備同步成功了,因為建立 Headless Service 之後,redis-sfs-0.redis-sfs.default.svc.cluster.local 在叢集中就是唯一可訪問的了。
➜ ~ kubectl create -f service.yamlservice/redis-sfs created➜ ~ kubectl get serviceNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEkubernetes ClusterIP 10.96.0.1 <none> 443/TCP 24dredis-sfs ClusterIP None <none> 6379/TCP 33s➜ ~ kubectl logs -f redis-sfs-1...1:S 05 Nov 2021 08:23:31.341 * Connecting to MASTER redis-sfs-0.redis-sfs:63791:S 05 Nov 2021 08:23:31.345 * MASTER <-> REPLICA sync started1:S 05 Nov 2021 08:23:31.345 * Non blocking connect for SYNC fired the event.1:S 05 Nov 2021 08:23:31.346 * Master replied to PING, replication can continue...1:S 05 Nov 2021 08:23:31.346 * Partial resynchronization not possible (no cached master)1:S 05 Nov 2021 08:23:31.348 * Full resync from master: 29d1c03da6ee2af173b8dffbb85b6ad504ccc28f:01:S 05 Nov 2021 08:23:31.425 * MASTER <-> REPLICA sync: receiving 175 bytes from master to disk1:S 05 Nov 2021 08:23:31.426 * MASTER <-> REPLICA sync: Flushing old data1:S 05 Nov 2021 08:23:31.426 * MASTER <-> REPLICA sync: Loading DB in memory1:S 05 Nov 2021 08:23:31.431 * Loading RDB produced by version 6.2.61:S 05 Nov 2021 08:23:31.431 * RDB age 0 seconds1:S 05 Nov 2021 08:23:31.431 * RDB memory usage when created 1.83 Mb1:S 05 Nov 2021 08:23:31.431 # Done loading RDB, keys loaded: 0, keys expired: 0.1:S 05 Nov 2021 08:23:31.431 * MASTER <-> REPLICA sync: Finished with success^C➜ ~ kubectl exec -it redis-sfs-1 -- bashroot@redis-sfs-1:/data# redis-cli -h redis-sfs-0.redis-sfs.default.svc.cluster.localredis-sfs-0.redis-sfs.default.svc.cluster.local:6379> pingPONGredis-sfs-0.redis-sfs.default.svc.cluster.local:6379>
此時無論我們刪除哪個 Pod,它都會按照原來的名稱被拉起來,從而可以保證準備關係,這個例子只是一個 StatefulSet 的示例,分析下來可以發現,雖然它可以維護主備關係,但是當主掛了的時候,此時備無法切換上來,因為沒有元件可以幫我們做這個切換操作,一個辦法是用 Redis Sentinel,可以參考這個專案的配置:k8s-redis-ha-master,如果你的 k8s 較新,需要 merge 此 PR.

六  Operator

雖然有了 StatefulSet,但是這隻能對基礎版有用,如果想自己定製更加複雜的操作,k8s 的解法是 operator,簡而言之,operator 就是定製自己 k8s 物件及物件所對應操作的解法。
那什麼是物件呢?一個 Redis 叢集,一個 etcd 叢集,zk 叢集,都可以是一個物件,現實中我們想描述什麼,就來定義什麼,實際上我們定一個是k8s yaml 中的 kind,之前的例子中,我們使用過 Pod,Deployment,StatefulSet,它們是 k8s 預設實現,現在如果要定義自己的物件,有兩個流程:
  • 定義物件,比如你的叢集預設有幾個節點,都有啥元件
  • 定義物件觸發的操作,當建立物件時候要做什麼流程,HA 時候要做什麼流程等
operator 的方式是基於程式設計實現的,可以用多種語言,用的最多的就是 go 語言,通常大家會藉助 operator-sdk 來完成,因為有很多程式碼會自動生成。相當於 operator 會生成框架,然後我們實現對應的業務邏輯。

1  準備工作

  • 安裝好 go 環境
  • 安裝 operator-sdk

2  初始化專案

然後我們按照官網的 sdk 例子,來一步一步實現一個 memcached 的 operator,這裡也可以換成 Redis,但是為了保證和官網一致,我們就按照官網來建立 memcached operator。
➜ ~ cd $GOPATH/src➜ src mkdir memcached-operator➜ src cd memcached-operator➜ memcached-operator operator-sdk init --domain yangbodong22011 --repo github.com/yangbodong22011/memcached-operator --skip-go-version-check // 這裡需要注意 domain 最好是和你在 https://hub.docker.com 的註冊名稱相同,因為後續會發布 docker 映象Writing kustomize manifests for you to edit...Writing scaffold for you to edit...Get controller runtime:$ go get sigs.k8s.io/[email protected]Update dependencies:$ go mod tidyNext: define a resource with:$ operator-sdk create api

3  建立 API 和 Controller

➜ memcached-operator operator-sdk create api --group cache --version v1alpha1 --kind Memcached --resource --controllerWriting kustomize manifests for you to edit...Writing scaffold for you to edit...api/v1alpha1/memcached_types.gocontrollers/memcached_controller.goUpdate dependencies:$ go mod tidyRunning make:$ make generatego: creating new go.mod: module tmpDownloading sigs.k8s.io/controller-tools/cmd/[email protected]go get: installing executables with 'go get' in module mode is deprecated. To adjust and download dependencies of the current module, use 'go get -d'. To install using requirements of the current module, use 'go install'. To install ignoring the current module, use 'go install' with a version, like 'go install example.com/cmd@latest'. For more information, see https://golang.org/doc/go-get-install-deprecation or run 'go help get' or 'go help install'....go get: added sigs.k8s.io/yaml v1.2.0/Users/yangbodong/go/src/memcached-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."➜ memcached-operator
上面的步驟實際上生成了一個 operator 的框架,接下來我們首先來定義 memcached 叢集都包括啥,將預設實現修改為 Size,表示一個 Memcached 叢集中 Memcached 的數量,最後呼叫 make generate 和 make manifests 來自動生成 deepcopy 和 CRD 資源。
➜ memcached-operator vim api/v1alpha1/memcached_types.go // 修改下面 Memcached 叢集的定義// MemcachedSpec defines the desired state of Memcachedtype MemcachedSpec struct { //+kubebuilder:validation:Minimum=0 // Size is the size of the memcached deployment Size int32 `json:"size"`}// MemcachedStatus defines the observed state of Memcachedtype MemcachedStatus struct { // Nodes are the names of the memcached pods Nodes []string `json:"nodes"`}➜ memcached-operator make generate/Users/yangbodong/go/src/memcached-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."➜ memcached-operator make manifests/Users/yangbodong/go/src/memcached-operator/bin/controller-gen "crd:trivialVersions=true,preserveUnknownFields=false" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases➜ memcached-operator

4  實現 Controller

接下來是第二步,定義當建立一個 Memcached 叢集時候,具體要幹啥。
➜ memcached-operator vim controllers/memcached_controller.gohttps://raw.githubusercontent.com/operator-framework/operator-sdk/latest/testdata/go/v3/memcached-operator/controllers/memcached_controller.go //將 example 換成 yangbodong22011,注意,// 註釋中的也要換,實際不是註釋,而是一種格式➜ memcached-operator go mod tidy; make manifests/Users/yangbodong/go/src/memcached-operator/bin/controller-gen "crd:trivialVersions=true,preserveUnknownFields=false" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases

5  釋出 operator 映象

➜ memcached-operator vim Makefile將 -IMG ?= controller:latest 改為 +IMG ?= $(IMAGE_TAG_BASE):$(VERSION)➜ memcached-operator docker login // 提前登入下 dockerLogin with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.Username: yangbodong22011Password:WARNING! Your password will be stored unencrypted in /Users/yangbodong/.docker/config.json.Configure a credential helper to remove this warning. Seehttps://docs.docker.com/engine/reference/commandline/login/#credentials-storeLogin Succeeded➜ memcached-operator sudo make docker-build docker-push ...=> => writing image sha256:a7313209e321c84368c5cb7ec820fffcec2d6fcb510219d2b41e3b92a2d5545a 0.0s => => naming to docker.io/yangbodong22011/memcached-operator:0.0.1 0.0sfac03a24e25a: Pushed6d75f23be3dd: Pushed0.0.1: digest: sha256:242380214f997d98186df8acb9c13db12f61e8d0f921ed507d7087ca4b67ce59 size: 739

6  修改映象和部署

➜ memcached-operator vim config/manager/manager.yamlimage: controller:latest 修改為 yangbodong22011/memcached-operator:0.0.1➜ memcached-operator vim config/default/manager_auth_proxy_patch.yaml因為國內訪問不了 gcr.ioimage: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 修改為 kubesphere/kube-rbac-proxy:v0.8.0 ➜ memcached-operator make deploy...configmap/memcached-operator-manager-config createdservice/memcached-operator-controller-manager-metrics-service createddeployment.apps/memcached-operator-controller-manager created➜ memcached-operator kubectl get deployment -n memcached-operator-system // ready 說明 operator 已經部署了NAME READY UP-TO-DATE AVAILABLE AGEmemcached-operator-controller-manager 1/1 1 1 31s➜ memcached-operator

7  建立 Memcached 叢集

➜ memcached-operator cat config/samples/cache_v1alpha1_memcached.yamlapiVersion: cache.yangbodong22011/v1alpha1kind: Memcachedmetadata: name: memcached-samplespec: size: 1➜ memcached-operator kubectl apply -f config/samples/cache_v1alpha1_memcached.yamlmemcached.cache.yangbodong22011/memcached-sample created➜ memcached-operator kubectl get podsNAME READY STATUS RESTARTS AGEmemcached-sample-6c765df685-xhhjc 1/1 Running 0 104sredis 1/1 Running 0 177mredis-deployment-866c4c6cf9-zskkb 1/1 Running 0 3h4mredis-sfs-0 1/1 Running 0 112mredis-sfs-1 1/1 Running 0 112m➜ memcached-operator
可以透過 kubectl logs 來檢視 operator 的日誌:
➜ ~ kubectl logs -f deployment/memcached-operator-controller-manager -n memcached-operator-system2021-11-05T09:50:46.042Z INFO controller-runtime.manager.controller.memcached Creating a new Deployment {"reconciler group": "cache.yangbodong22011", "reconciler kind": "Memcached", "name": "memcached-sample", "namespace": "default", "Deployment.Namespace": "default", "Deployment.Name": "memcached-sample"}
至此,我們的 operator-sdk 的任務暫時告一段落。

七  總結

本文介紹了 k8s 的架構,各元件的功能,以及透過一個循序漸進的 Redis 例子介紹了 k8s 中 Pod, Deployment, StatefulSet 的概念,並透過 operator-sdk 演示了一個完整的 operator製作的例子。

八  參考資料

[1] 《深入剖析Kubernetes》張磊,CNCF TOC 成員,at 阿里巴巴。

[2] 《Kubernetes 權威指南》第五版

[3] 《Large-scale cluster management at Google with Borg》

https://research.google/pubs/pub43438/
[4] https://www.redhat.com/zh/topics/containers/what-is-kubernetes?
[5] https://www.infoworld.com/article/3632142/how-docker-broke-in-half.html?
[6] https://landscape.cncf.io/
[7] https://docs.docker.com/desktop/kubernetes/
[8] https://minikube.sigs.k8s.io/docs/start/
[9] https://www.aliyun.com/product/kubernetes?
[10] https://github.com/kubernetes/kubeadm
[11] https://www.cnblogs.com/chiangchou/p/k8s-1.html
[12] https://github.com/tarosky/k8s-redis-ha
[13https://sdk.operatorframework.io/docs/installation/

2021年阿里雲開源映象站最熱門映象王全梳理!
阿里雲開源映象站的初衷在於宣傳自由軟體的價值,提高大家的開發效率,幫助大家更快地進行應用建立。點選閱讀原文檢視詳情!


相關文章