前言
翻看了很多的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= \
--service-cluster-ip-range=10.0.0.0/ \
--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= 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: 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: get svc # 查看service,与/registry/services/specs/default/kubernetes对应
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.0.0.1 <none> /TCP 1h
查看endpionts:
$ bin/kubectl -s 127.0.0.1: get ep #查看endpoints,与/registry/services/endpoints/default/kubernetes对应
NAME ENDPOINTS AGE
kubernetes xxxxx: 1h
启动controller-manager
注:这个命令虽然可以启动controller-manager,但后续会出现一些问题,需要再添加参数,见“测试”部分对controller-manaer的调整;
bin/kube-controller-manager \
--master=127.0.0.1: \
--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: get sa
NAME SECRETS AGE
default 3m
这里的SECRETS为0,表明没有为这个service account生成secret,这个也是与安全相关的东西,先忽略,后面会遇到问题,然后我们再回来处理它;
再看一下endpoint:
$ bin/kubectl -s 127.0.0.1: 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:
生成的数据
查看etcd,多出现一些节点:
/registry/events/kube-system/kube-scheduler.153c41b5b3052d28 /registry/services/endpoints/kube-system/kube-scheduler
使用kubectl查看信息
再看一下endpoints
$ bin/kubectl -s 127.0.0.1: get ep --namespkube-system
NAME ENDPOINTS AGE
kube-controller-manager <none> 15m
kube-scheduler <none> 1m
查看集群状态
$ bin/kubectl -s 127.0.0.1: get cs
NAME STATUS MESSAGE ERROR
controller-manager Healthy ok
scheduler Healthy ok
etcd- 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: get node
NAME STATUS ROLES AGE VERSION
<node> Ready <none> 38m v1.10.5
启动kube-proxy
bin/kube-proxy \
--master=<apiserver>: \
--proxy-mode=iptables \
--logtostderr=true
参数说明:
- proxy-mode:当前默认使用的就是iptables模式,ipvs还是experiment功能;如果iptables不适用,则回退到userspace模式下;
至此,node部署完成;
测试
启动应用
到apiserver上,执行以下命令,运行一个标准的nginx容器,为了省时间,我也把它拉下来,push到私有registry上了:
$ bin/kubectl -s 127.0.0.1: run nginx --image=<registry>/nginx/nginx --port=
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: \
--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: get sa
NAME SECRETS AGE
default 2h
再重新试一下启动nginx,并查看状态;
$ bin/kubectl -s 127.0.0.1: run nginx --image=<registry>/nginx/nginx --port= # 启动
$
$ bin/kubectl -s 127.0.0.1: get deploy # 查看deploy
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
nginx 1m
$
$ bin/kubectl -s 127.0.0.1: describe deploy nginx|grep NewReplicaSet # 查看deploy详情
Progressing True NewReplicaSetAvailable
NewReplicaSet: nginx-55cc995fdb (/ replicas created)
$
$ bin/kubectl -s 127.0.0.1: describe replicasets nginx-55cc995fdb |tail -1 # 查看replicaset详情
Normal SuccessfulCreate 10m replicaset-controller Created pod: nginx-55cc995fdb-27t7z
$
$ bin/kubectl -s 127.0.0.1: get pod nginx-55cc995fdb-27t7z -o wide # 查看pod
NAME READY STATUS RESTARTS AGE IP NODE
nginx-55cc995fdb-27t7z / Running 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…" seconds ago Up seconds k8s_nginx_nginx-55cc995fdb-27t7z_default_1ca63ddd-7ab5-11e8-be61-3440b59f0098_0
ec311fee295d <registry>/rhel7/pod-infrastructure:latest "/pod" seconds ago Up 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: expose deployment nginx --type=NodePort --name=example-service
service "example-service" exposed
$
$ bin/kubectl -s 127.0.0.1: 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> /TCP
TargetPort: /TCP
NodePort: <unset> /TCP
Endpoints: 172.10.63.2:
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>:
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 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