前言
翻看了很多的kubernetes的安裝教程,也反復做了一些實驗,深感教程之復雜,所以決定寫一個極簡版本的安裝教程,目標在於用盡可能少的參數啟動服務,並且剖析各組件關系,然后再在此基礎上逐步添加參數,實現功能完備;
干什么
- 在server節點上啟動三個主要服務:apiserver、controller-manager、scheduler;
- 在node節點上啟動兩個主要服務:kubelet、kube-proxy;
- 試驗啟動容器,且測試容器內服務的功能;
不干什么
- 不通過軟件源安裝;
- 不添加systemd服務;
- 盡量不做安全配置;不做bootstrap流程,不用kubeadm創建集群;
- 不做高可用配置;
- 不用ipvs做負載均衡;
- 不裝任何插件(dns, dashboard, etc);
說明:
- 如果通過軟件源安裝,看似會簡化部署流程,但事實上會引入一些別的問題,因為一方面解決版本配套要添加合適的源比較麻煩,另一方面發布包在操作系統上做的一些配置也很復雜,為迎合它也需要做一些復雜的工作;(其實就是源里的東西版本低了不想用這種大實話我是絕對不會說的)
- 個人感覺安全配置是提高了kubernetes入門門檻的關鍵原因,本身安全認證授權就是非常復雜的機制,與業務獨立,一般人較少接觸,任何一個新入門kubernetes的人在瞎子摸象之前要先搞明白這些東西就要花很多功夫;
但是,非安全的服務端口正在被deperecate,看一下今年4月社區contributer的表態,自己感受一下吧,https://github.com/kubernetes/kubernetes/pull/59018#issuecomment-381583629。
未來這一部分可能是造成本文失效的最主要原因;
准備
架構概述
先大概知道一下架構,借用一張圖,來源:https://www.kubernetes.org.cn/4047.html
版本及依賴
etcd | 3.3.8 |
docker | 18.03.1-ce |
flannel | 0.10.0 |
kubernetes | 1.10.5 |
etcd是一個基礎組件,沒有太復雜的依賴關系,沒什么好說的;
docker見之前的docker安裝流程;
flannel見之前的flannel安裝流程;
下載程序包
到官方changelog里找downloads:https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.10.md
只下載server和node包就夠了,client包只有一個kubectl工具,在前兩個里都有帶;
Server部署
server有三個組件,apiserver是接口服務,對接etcd,做存儲邏輯的封裝,后面跟着controller-manager和scheduler,做一些后台控制邏輯;
整個server做的事情全都圍繞etcd轉,所以根本不需要系統的root權限,普通用戶足夠;
安裝server
解壓server包,程序都解壓到了kubernetes/server/bin路徑:
$ tar vfxz kubernetes-server-linux-amd64.tar.gz # 解壓 $ ls kubernetes/server/bin #看一下
$ cd kubernetes/server # 就在這個路徑下做server服務的管理
啟動apiserver
bin/kube-apiserver \
--cert-dir=etc/kubernetes/cert \ --insecure-bind-address=0.0.0.0 \ --insecure-port=18080 \ --service-cluster-ip-range=10.0.0.0/16 \ --etcd-servers=http://<etcd>:2379 \ --logtostderr=true
參數說明:
- --cert-dir:雖然我不想做安全配置,但奈何kubernetes的https server是必選項,一定要有TLS證書和密鑰才行,如果沒有提供自定義的證書密鑰,就會自動創建一個;如果不設這個參數,證書和密鑰會創建到/var/run/kubernetes下;
- --insecure-bind-address,--insecure-port:非安全服務地址和端口,以后會廢棄;
- --service-cluster-ip-range:service cluster ip是提供給service的虛擬ip,因而這里應當使用一個虛擬段,以免與物理ip段混用造成路由混亂;
- --etcd-servers:kubernetes強依賴etcd服務;
生成的文件及數據
apiserver啟動后,在cert-dir下出現了自動創建的證書和密鑰:
$ file etc/kubernetes/cert/* etc/kubernetes/cert/apiserver.crt: PEM certificate etc/kubernetes/cert/apiserver.key: PEM RSA private key
在etcd上出現了一些數據:
# ETCDCTL_API=3 etcdctl get / --prefix --keys-only /registry/apiregistration.k8s.io/apiservices/v1. /registry/apiregistration.k8s.io/apiservices/v1.apps /registry/apiregistration.k8s.io/apiservices/v1.authentication.k8s.io /registry/apiregistration.k8s.io/apiservices/v1.authorization.k8s.io /registry/apiregistration.k8s.io/apiservices/v1.autoscaling /registry/apiregistration.k8s.io/apiservices/v1.batch /registry/apiregistration.k8s.io/apiservices/v1.networking.k8s.io /registry/apiregistration.k8s.io/apiservices/v1.rbac.authorization.k8s.io /registry/apiregistration.k8s.io/apiservices/v1.storage.k8s.io /registry/apiregistration.k8s.io/apiservices/v1beta1.admissionregistration.k8s.io /registry/apiregistration.k8s.io/apiservices/v1beta1.apiextensions.k8s.io /registry/apiregistration.k8s.io/apiservices/v1beta1.apps /registry/apiregistration.k8s.io/apiservices/v1beta1.authentication.k8s.io /registry/apiregistration.k8s.io/apiservices/v1beta1.authorization.k8s.io /registry/apiregistration.k8s.io/apiservices/v1beta1.batch /registry/apiregistration.k8s.io/apiservices/v1beta1.certificates.k8s.io /registry/apiregistration.k8s.io/apiservices/v1beta1.events.k8s.io /registry/apiregistration.k8s.io/apiservices/v1beta1.extensions /registry/apiregistration.k8s.io/apiservices/v1beta1.policy /registry/apiregistration.k8s.io/apiservices/v1beta1.rbac.authorization.k8s.io /registry/apiregistration.k8s.io/apiservices/v1beta1.storage.k8s.io /registry/apiregistration.k8s.io/apiservices/v1beta2.apps /registry/apiregistration.k8s.io/apiservices/v2beta1.autoscaling /registry/namespaces/default /registry/namespaces/kube-public /registry/namespaces/kube-system /registry/ranges/serviceips /registry/ranges/servicenodeports /registry/services/endpoints/default/kubernetes /registry/services/specs/default/kubernetes
忽略掉前面的/register/apixxx,來看一下后面這些東西:
- 三個namespace:default、kube-public、kube-system;
- /register/ranges/serviceips:內容包含10.0.0.0/16,與--service-cluster-ip-range一致;
- /registry/ranges/servicenodeports:內容包含30000-32767,查了一下代碼,這個值是--service-node-port-range參數的默認值,見https://github.com/kubernetes/kubernetes/blob/release-1.10/pkg/kubeapiserver/options/options.go;
- /registry/services/<endpoints,specs>/default/kubernetes:描述了同一個service(kubernetes)的endpoints信息及specs信息,endpoints內容可見server的外網ip,specs內容可見10.0.0.1,這是service-cluster-ip-range的第一個ip地址;
使用kubectl查看信息
apiserver提供了restapi以獲取和管理在etcd上的集群狀態信息;kubectl就是這個restapi的客戶端;
因為我們的服務端口不在默認的8080上,所以使用時要加一個-s參數:
查看namespace:
$ bin/kubectl -s 127.0.0.1:18080 get ns # 查看三個namespace,與etcd的/registry/namespaces對應 NAME STATUS AGE default Active 1h kube-public Active 1h kube-system Active 1h
查看service:
$ bin/kubectl -s 127.0.0.1:18080 get svc # 查看service,與/registry/services/specs/default/kubernetes對應 NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 1h
查看endpionts:
$ bin/kubectl -s 127.0.0.1:18080 get ep #查看endpoints,與/registry/services/endpoints/default/kubernetes對應 NAME ENDPOINTS AGE kubernetes xxxxx:6443 1h
啟動controller-manager
注:這個命令雖然可以啟動controller-manager,但后續會出現一些問題,需要再添加參數,見“測試”部分對controller-manaer的調整;
bin/kube-controller-manager \ --master=127.0.0.1:18080 \ --logtostderr=true
生成的數據
查看etcd,多出現一些節點:
/registry/events/kube-system/kube-controller-manager.153c40f68e70e209 /registry/serviceaccounts/default/default /registry/serviceaccounts/kube-public/default /registry/serviceaccounts/kube-system/default /registry/services/endpoints/kube-system/kube-controller-manager
多出一個event,三個serviceaccounts(三個namespace下的default),以及一個endpoints;
使用kubectl查看信息
event就不看了,來看一下service account:
$ bin/kubectl -s 127.0.0.1:18080 get sa NAME SECRETS AGE default 0 3m
這里的SECRETS為0,表明沒有為這個service account生成secret,這個也是與安全相關的東西,先忽略,后面會遇到問題,然后我們再回來處理它;
再看一下endpoint:
$ bin/kubectl -s 127.0.0.1:18080 get ep --namespace kube-system NAME ENDPOINTS AGE kube-controller-manager <none> 7m
因為kube-controller-manager在kube-system下,所以需要多加一個namespace參數;
啟動scheduler
bin/kube-scheduler \ --master=127.0.0.1:18080
生成的數據
查看etcd,多出現一些節點:
/registry/events/kube-system/kube-scheduler.153c41b5b3052d28
/registry/services/endpoints/kube-system/kube-scheduler
使用kubectl查看信息
再看一下endpoints
$ bin/kubectl -s 127.0.0.1:18080 get ep --namespkube-system NAME ENDPOINTS AGE kube-controller-manager <none> 15m kube-scheduler <none> 1m
查看集群狀態
$ bin/kubectl -s 127.0.0.1:18080 get cs NAME STATUS MESSAGE ERROR controller-manager Healthy ok scheduler Healthy ok etcd-0 Healthy {"health":"true"}
至此,server就算部署好了
node部署
本想node的兩個服務組件也用非root用戶的,但試下來發現不行,kubelet需要與docker交互,而kube-proxy則要改iptables,都需要提供root權限;
安裝node
解壓node包,程序都解壓到了kubernetes/node/bin路徑:
# tar vfxz kubernetes-node-linux-amd64.tar.gz #解壓 # ls kubernetes/node/bin/ # 看一下 # cd kubernetes/node # 就在這個路徑下進行node服務的管理
啟動kubelet
kubelet的參數太多了,可能為了簡化kubelet的啟動腳本吧,引入了兩個配置文件,兩個,兩個......而且除這兩個文件外還要設置其它參數,設置其它參數,其它參數......faint
先生成訪問apiserver需要用的kubeconfig文件:
# KUBE_APISERVER="http://<apiserver>:18080" #
# bin/kubectl config set-cluster kubernetes \ --server=$KUBE_APISERVER \ --kubeconfig=etc/kubernetes/kubelet.kubeconfig #
# bin/kubectl config set-context default \ --cluster=kubernetes \ --user=default-noauth \ --kubeconfig=etc/kubernetes/kubelet.kubeconfig #
# bin/kubectl config use-context default --kubeconfig=etc/kubernetes/kubelet.kubeconfig #
# bin/kubectl config view --kubeconfig=etc/kubernetes/kubelet.kubeconfig apiVersion: v1 clusters: - cluster: server: http://<apiserver>:18080 name: kubernetes contexts: - context: cluster: kubernetes user: "" name: default current-context: default kind: Config preferences: {} users: []
這個配置文件就聲明了一個以http://<apiserver>:18080為入口的名為“kubernetes”的集群,以及一個匿名訪問“kubernetes”集群的名為“default”的上下文,並聲明使用這個"default"上下文;
寫kubelet自身需要的配置文件(這個文件可以是yaml或json格式,因為很多教程用了json格式,所以這里我用一下yaml格式):
# cat etc/kubernetes/kubelet.config.yaml kind: KubeletConfiguration apiVersion: kubelet.config.k8s.io/v1beta1 cgroupDriver: cgroupfs
kind和apiVersion都是定死的,可見https://kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file/,以及代碼的這里:https://github.com/kubernetes/kubernetes/blob/release-1.10/pkg/kubelet/apis/kubeletconfig/v1beta1/register.go;
cgroupDriver需要與docker的真實情況相符,通過以下命令查看:
# docker info|grep 'Cgroup Driver' Cgroup Driver: cgroupfs
啟動kubelet:
bin/kubelet \ --cert-dir=etc/kubernetes/cert \ --kubeconfig=etc/kubernetes/kubelet.kubeconfig \ --config=etc/kubernetes/kubelet.config.yaml \ --pod-infra-container-image=<registry>/rhel7/pod-infrastructure:latest \ --runtime-cgroups=/systemd/system.slice --kubelet-cgroups=/systemd/system.slice \ --logtostderr=true
參數說明:
- --cert-dir:與apiserver的參數道理相同;kubernete強制生成;
- --kubeconfig:訪問apiserver需要的config;
- --config:kubelet自己需要的config;
- --pod-infra-container-image:一個基礎容器鏡像,做為pod的第一個容器啟動,為每個pod提供network namespace;沒啥業務邏輯,理論上說就是啟動之后保持不退出即可,以后我可以自己寫一個試試。默認鏡像地址可能被牆,所以想辦法下載一個下來(docker官方registry上有),然后推到私有的registry比較好;
- --runtime-cgroups,--kubelet-cgroups:可能是因為我手動啟動kubelet,而不是做為systemd的service啟動的緣故吧,啟動之后會時而出現這個錯誤:Failed to get system container stats for "/user.slice/user-0.slice/,加上這兩個參數就好了;
生成的文件及數據
在cert-dir下出現了自動創建的證書和密鑰:
# file etc/kubernetes/cert/* etc/kubernetes/cert/kubelet.crt: PEM certificate etc/kubernetes/cert/kubelet.key: PEM RSA private key
多說一句,這個證書是node自己簽發的,肯定得不到apiserver的認可;當然因為我在這里不做安全集群,這就無所謂;但在安全集群里,kubelet的證書會由controller-manager簽發;
在etcd上出現了一些新的數據,除去events外,就一個最重要的minions數據:
/registry/minions/<node>
使用kubectl查看信息
$ bin/kubectl -s 127.0.0.1:18080 get node NAME STATUS ROLES AGE VERSION <node> Ready <none> 38m v1.10.5
啟動kube-proxy
bin/kube-proxy \ --master=<apiserver>:18080 \ --proxy-mode=iptables \ --logtostderr=true
參數說明:
- proxy-mode:當前默認使用的就是iptables模式,ipvs還是experiment功能;如果iptables不適用,則回退到userspace模式下;
至此,node部署完成;
測試
啟動應用
到apiserver上,執行以下命令,運行一個標准的nginx容器,為了省時間,我也把它拉下來,push到私有registry上了:
$ bin/kubectl -s 127.0.0.1:18080 run nginx --image=<registry>/nginx/nginx --port=80
pod啟動失敗,報錯:No API token found for service account "default"
之前遺留了一個問題,見service_account_without_secrets:
於是調整一下controller-manager的參數,加上--service-account-private-key-file和--root-ca-file參數,重啟controller-manager:
$ bin/kube-controller-manager \ --master=127.0.0.1:18080 \ --service-account-private-key-file=etc/kubernetes/cert/apiserver.key \ --root-ca-file=etc/kubernetes/cert/apiserver.crt \ --logtostderr=true
再看一下service account的情況,secrets已經不是0了:
$ bin/kubectl -s 127.0.0.1:18080 get sa NAME SECRETS AGE default 1 2h
再重新試一下啟動nginx,並查看狀態;
$ bin/kubectl -s 127.0.0.1:18080 run nginx --image=<registry>/nginx/nginx --port=80 # 啟動
$ $ bin/kubectl -s 127.0.0.1:18080 get deploy # 查看deploy NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE nginx 1 1 1 1 1m
$ $ bin/kubectl -s 127.0.0.1:18080 describe deploy nginx|grep NewReplicaSet # 查看deploy詳情 Progressing True NewReplicaSetAvailable NewReplicaSet: nginx-55cc995fdb (1/1 replicas created)
$ $ bin/kubectl -s 127.0.0.1:18080 describe replicasets nginx-55cc995fdb |tail -1 # 查看replicaset詳情 Normal SuccessfulCreate 10m replicaset-controller Created pod: nginx-55cc995fdb-27t7z
$ $ bin/kubectl -s 127.0.0.1:18080 get pod nginx-55cc995fdb-27t7z -o wide # 查看pod NAME READY STATUS RESTARTS AGE IP NODE nginx-55cc995fdb-27t7z 1/1 Running 0 11m 172.10.63.2 <node>
再到node上看一下容器狀態:
# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3e5e69a69950 <registry>/nginx/nginx "nginx -g 'daemon of…" 28 seconds ago Up 27 seconds k8s_nginx_nginx-55cc995fdb-27t7z_default_1ca63ddd-7ab5-11e8-be61-3440b59f0098_0 ec311fee295d <registry>/rhel7/pod-infrastructure:latest "/pod" 28 seconds ago Up 27 seconds k8s_POD_nginx-55cc995fdb-27t7z_default_1ca63ddd-7ab5-11e8-be61-3440b59f0098_0
看以看到,同時啟動了兩個容器,一個是pod-infrastructure,另一個是nginx;如果進入這兩個容器內看一下的話,會發現它們的ip地址是同一個;
總結一下啟動應用時這些資源的關系:
- 一個應用會對應一個deploy資源;
- 一個deploy會對應一個replicaset資源;
- 一個replicaset會對應多個(默認為一個)pod資源;
- 一個pod會在某個node上啟動至少兩個container,其中一個是pod-infrastructure運行容器,另一個是應用所在的運行容器;它們共享ip地址;
暴露服務
如果啟動的應用本身是個服務的話,還需要將服務地址暴露出來,在server(master)上運行:
$ bin/kubectl -s 127.0.0.1:18080 expose deployment nginx --type=NodePort --name=example-service service "example-service" exposed $ $ bin/kubectl -s 127.0.0.1:18080 describe services example-service Name: example-service Namespace: default Labels: run=nginx Annotations: <none> Selector: run=nginx Type: NodePort IP: 10.0.158.97 Port: <unset> 80/TCP TargetPort: 80/TCP NodePort: <unset> 30152/TCP Endpoints: 172.10.63.2:80 Session Affinity: None External Traffic Policy: Cluster Events: <none>
從后面的服務描述中得到以下信息:
- service的cluster_ip是10.0.158.97,這個地址在apiserver啟動時的--service-cluster-ip-range參數范圍內,也即etcd上/register/ranges/serviceips節點內配置的信息;
- service的nodeport是30152,這個端口在apiserver啟動時的--service-node-port-range參數范圍內,也即etcd上/registry/ranges/servicenodeports節點內配置的信息;
- service的endpoints是172.10.63.2:80,這是ip地址是容器內的地址;
測試服務
在server(master)上訪問node的30152端口,nginx服務正常:
$ curl <node>:30152 <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html>
在node上訪問10.0.158.97:80,nginx也服務正常;查看一下node的iptables,會發現有一條規則將target為10.0.158.97:80的流量轉發到了本機的30152端口;這是kueb-proxy做的事情;
在另一個節點(可以不是kubernetes node,只要求啟用了與node相同的flanneld)或容器(可以不是kubernetes pod,只要求容器所在節點啟用了與node相同的flanneld)內訪問172.10.63.2:80,nginx也服務正常;這就是flanneld做的事情了;
重點參考以下三篇:
- https://www.jianshu.com/p/8358117a23bb
- https://github.com/opsnull/follow-me-install-kubernetes-cluster
- https://jimmysong.io/kubernetes-handbook/practice/install-kubernetes-on-centos.html