安装docker
设置存储库
[root@kubeadm ~]# yum install yum-utils -y
[root@kubeadm ~]# yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
安装DOCKER引擎
#安装最新版本
yum install docker-ce docker-ce-cli containerd.io
#安装指定版本,我这里安装18.09.9
[root@kubeadm ~]# yum install docker-ce-18.09.9 docker-ce-cli-18.09.9 containerd.io -y
[root@kubeadm ~]# systemctl start docker
[root@kubeadm ~]# systemctl enable docker
安装kubeadm,kubelet和kubectl
设置存储库
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=0
EOF
安装
yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
[root@kubeadm ~]# systemctl enable --now kubelet
关闭seliunx和防火墙
为了方便,这里直接关闭了seliunx和防火墙
[root@kubeadm ~]# sed -i s#=enforcing#=disabled#g /etc/selinux/config
[root@kubeadm ~]# setenforce 0
[root@kubeadm ~]# systemctl stop firewalld
[root@kubeadm ~]# systemctl disable firewalld
修改docker的cgroup driver与kubelet一致
cat <<EOF > /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"]
}
EOF
#重启docker
systemctl restart docker
#查看修改后状态
docker info | grep Cgroup
部署 Kubernetes 的 Master 节点
#安装master节点最简单的就是:
kubeadm init
#这里稍微提高一下难度:通过配置文件来开启一些实验性功能,编写了一个给 kubeadm 用的 YAML 文件:
[root@kubeadm ~]# vim kubeadm.yaml
apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
kubernetesVersion: v1.18.3
controllerManager:
extraArgs:
horizontal-pod-autoscaler-use-rest-clients: "true"
horizontal-pod-autoscaler-sync-period: "10s"
node-monitor-grace-period: "10s"
apiServer:
extraArgs:
runtime-config: "api/all=true"
[root@kubeadm ~]# kubeadm init --config kubeadm.yaml
执行kubeadm init后只需要几分钟就可以完成 Kubernetes Master 的部署了。部署完成后,kubeadm 会生成一行指令:
Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 192.168.37.201:6443 --token iq6n9j.sna0v1xtx3z3grem \
--discovery-token-ca-cert-hash sha256:712d83cf1641bc0417ce2a059ff966c7fd92f0287751d68f6980ce3b1ddf807a
执行一下上面kubeadm 提示我们第一次使用 Kubernetes 集群所需要的配置命令
[root@kubeadm ~]# mkdir -p $HOME/.kube
[root@kubeadm ~]# cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
[root@kubeadm ~]# chown $(id -u):$(id -g) $HOME/.kube/config
注:需要这些配置命令的原因是:Kubernetes 集群默认需要加密方式访问。所以,这几条命令,就是将刚刚部署生成的 Kubernetes 集群的安全配置文件,保存到当前用户的.kube 目录下,kubectl 默认会使用这个目录下的授权信息访问 Kubernetes 集群。
使用 kubectl get 命令来查看当前唯一一个节点的状态了:
[root@kubeadm ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
kubeadm NotReady master 137m v1.18.3
可以看到,这个 get 指令输出的结果里,Master 节点的状态是 NotReady,这是为什么呢?
在调试 Kubernetes 集群时,最重要的手段就是用 kubectl describe 来查看这个节点(Node)对象的详细信息、状态和事件(Event),我们来试一下:
[root@kubeadm ~]# kubectl describe node kubeadm
...
Conditions:
...
Ready False ... KubeletNotReady runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized
通过 kubectl describe 指令的输出,我们可以看到 NodeNotReady 的原因在于,我们尚未部署任何网络插件。
另外,我们还可以通过 kubectl 检查这个节点上各个系统 Pod 的状态,其中,kube-system 是 Kubernetes 项目预留的系统 Pod 的工作空间
[root@kubeadm ~]# kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-66bff467f8-dh5wj 0/1 Pending 0 148m
coredns-66bff467f8-lh8r7 0/1 Pending 0 148m
etcd-kubeadm 1/1 Running 0 148m
kube-apiserver-kubeadm 1/1 Running 0 148m
kube-controller-manager-kubeadm 1/1 Running 0 148m
kube-proxy-9tkrs 1/1 Running 0 148m
kube-scheduler-kubeadm 1/1 Running 0 148m
可以看到,CoreDNS 等依赖于网络的 Pod 都处于 Pending 状态,即调度失败。这当然是符合预期的:因为这个 Master 节点的网络尚未就绪。
部署网络插件
在 Kubernetes 项目“一切皆容器”的设计理念指导下,部署网络插件非常简单,只需要执行一句 kubectl apply 指令,以 Weave 为例:
kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"
部暑完成后,我们过一两分钟再观察一下这个节点上各个系统 Pod 的状态:
[root@kubeadm ~]# kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-66bff467f8-dh5wj 1/1 Running 0 3h6m
coredns-66bff467f8-lh8r7 1/1 Running 0 3h6m
etcd-kubeadm 1/1 Running 0 3h6m
kube-apiserver-kubeadm 1/1 Running 0 3h6m
kube-controller-manager-kubeadm 1/1 Running 0 3h6m
kube-proxy-9tkrs 1/1 Running 0 3h6m
kube-scheduler-kubeadm 1/1 Running 0 3h6m
weave-net-tms68 2/2 Running 0 2m5s
Kubernetes 支持容器网络插件,使用的是一个名叫 CNI 的通用接口,它也是当前容器网络的事实标准,市面上的所有容器网络开源项目都可以通过 CNI 接入 Kubernetes,比如 Flannel、Calico、Canal、Romana 等等,它们的部署方式也都是类似的“一键部署”。
至此,Kubernetes 的 Master 节点就部署完成了。如果你只需要一个单节点的 Kubernetes,现在你就可以使用了。
部署 Kubernetes 的 Worker 节点
1、按照前面的文档安装docker和kubeadm,kubelet,kubectl
2、执行部署 Master 节点时生成的 kubeadm join 指令
kubeadm join 192.168.37.201:6443 --token iq6n9j.sna0v1xtx3z3grem \
--discovery-token-ca-cert-hash sha256:712d83cf1641bc0417ce2a059ff966c7fd92f0287751d68f6980ce3b1ddf807a
通过 Taint/Toleration 调整 Master 执行 Pod 的策略
默认情况下 Master 节点是不允许运行用户 Pod 的。而 Kubernetes 做到这一点,依靠的是 Kubernetes 的 Taint/Toleration 机制。
原理非常简单:一旦某个节点被加上了一个 Taint,即被“打上了污点”,那么所有 Pod 就都不能在这个节点上运行,因为 Kubernetes 的 Pod 都有“洁癖”。
除非,有个别的 Pod 声明自己能“容忍”这个“污点”,即声明了 Toleration,它才可以在这个节点上运行。
其中,为节点打上“污点”(Taint)的命令是:
$ kubectl taint nodes node1 foo=bar:NoSchedule
这时,该 node1 节点上就会增加一个键值对格式的 Taint,即:foo=bar:NoSchedule。其中值里面的 NoSchedule,意味着这个 Taint 只会在调度新 Pod 时产生作用,而不会影响已经在 node1 上运行的 Pod,哪怕它们没有 Toleration。
那么 Pod 又如何声明 Toleration 呢?
我们只要在 Pod 的.yaml 文件中的 spec 部分,加入 tolerations 字段即可:
apiVersion: v1
kind: Pod
...
spec:
tolerations:
- key: "foo"
operator: "Equal"
value: "bar"
effect: "NoSchedule"
这个 Toleration 的含义是,这个 Pod 能“容忍”所有键值对为 foo=bar 的 Taint( operator: “Equal”,“等于”操作)。现在回到我们已经搭建的集群上来。
这时,如果你通过 kubectl describe 检查一下 Master 节点的 Taint 字段,就会有所发现了:
$ kubectl describe node kubeadm
Name: kubeadm
Roles: master
Taints: node-role.kubernetes.io/master:NoSchedule
可以看到,Master 节点默认被加上了 node-role.kubernetes.io/master:NoSchedule这样一个“污点”,其中“键”是node-role.kubernetes.io/master,而没有提供“值”
此时,你就需要像下面这样用“Exists”操作符(operator: “Exists”,“存在”即可)来说明,该 Pod 能够容忍所有以 foo 为键的 Taint,才能让这个 Pod 运行在该 Master 节点上:
apiVersion: v1
kind: Pod
...
spec:
tolerations:
- key: "foo"
operator: "Exists"
effect: "NoSchedule"
当然,如果你就是想要一个单节点的 Kubernetes,删除这个 Taint 才是正确的选择:
$ kubectl taint nodes --all node-role.kubernetes.io/master-
或者,只是删除master节点taint,让master节点可以运行pod
$ kubectl taint nodes kubeadm node-role.kubernetes.io/master-
部署容器存储插件
Rook 项目是一个基于 Ceph 的 Kubernetes 存储插件(它后期也在加入对更多存储实现的支持)。不过,不同于对 Ceph 的简单封装,Rook 在自己的实现中加入了水平扩展、迁移、灾难备份、监控等大量的企业级功能,使得这个项目变成了一个完整的、生产级别可用的容器存储插件。
git clone --single-branch --branch {{ branchName }} https://github.com/rook/rook.git
cd rook/cluster/examples/kubernetes/ceph
kubectl create -f crds.yaml -f common.yaml -f operator.yaml
kubectl create -f cluster.yaml
在部署完成后,你就可以看到 Rook 项目会将自己的 Pod 放置在由它自己管理的Namespace中
[root@kubeadm ~]# kubectl get pods -n rook-ceph -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
csi-cephfsplugin-fqbqs 3/3 Running 0 125m 192.168.37.202 kube02 <none> <none>
csi-cephfsplugin-lrdn4 3/3 Running 0 125m 192.168.37.203 kube03 <none> <none>
csi-cephfsplugin-m2h4m 3/3 Running 0 92m 192.168.37.201 kubeadm <none> <none>
csi-cephfsplugin-provisioner-7459667b67-nntxx 5/5 Running 0 125m 10.36.0.4 kube03 <none> <none>
csi-cephfsplugin-provisioner-7459667b67-vzqlb 5/5 Running 0 125m 10.44.0.2 kube02 <none> <none>
csi-rbdplugin-cn4zk 3/3 Running 0 125m 192.168.37.203 kube03 <none> <none>
csi-rbdplugin-mvddz 3/3 Running 0 125m 192.168.37.202 kube02 <none> <none>
csi-rbdplugin-provisioner-7cbb778994-6tn9h 6/6 Running 0 125m 10.44.0.4 kube02 <none> <none>
csi-rbdplugin-provisioner-7cbb778994-htgch 6/6 Running 0 125m 10.36.0.3 kube03 <none> <none>
csi-rbdplugin-tv7sl 3/3 Running 0 92m 192.168.37.201 kubeadm <none> <none>
rook-ceph-crashcollector-kube02-84864475db-f7nt4 1/1 Running 0 92m 10.44.0.5 kube02 <none> <none>
rook-ceph-crashcollector-kube03-554dd78cbc-sgt6j 1/1 Running 0 88m 10.36.0.8 kube03 <none> <none>
rook-ceph-crashcollector-kubeadm-c5d646cc6-lfh5z 1/1 Running 0 91m 10.32.0.7 kubeadm <none> <none>
rook-ceph-mgr-a-7586874869-wjl5n 1/1 Running 1 88m 10.36.0.6 kube03 <none> <none>
rook-ceph-mon-a-855dc46d89-ktk4r 1/1 Running 0 92m 10.44.0.3 kube02 <none> <none>
rook-ceph-mon-b-b47dbffcd-zpjdj 1/1 Running 0 92m 10.36.0.5 kube03 <none> <none>
rook-ceph-mon-c-76bcbdf47f-v7hwt 1/1 Running 0 91m 10.32.0.5 kubeadm <none> <none>
rook-ceph-operator-567d7945d6-mrr26 1/1 Running 0 147m 10.36.0.1 kube03 <none> <none>
rook-ceph-osd-0-76fcb76b87-znrcn 1/1 Running 2 88m 10.44.0.6 kube02 <none> <none>
rook-ceph-osd-1-6d4cf6d9fb-6c4jj 1/1 Running 0 88m 10.36.0.9 kube03 <none> <none>
rook-ceph-osd-2-5556589849-9q6vd 1/1 Running 0 80m 10.32.0.6 kubeadm <none> <none>
rook-ceph-osd-prepare-kube02-sfvq7 0/1 Completed 0 88m 10.44.0.5 kube02 <none> <none>
rook-ceph-osd-prepare-kube03-j769j 0/1 Completed 0 88m 10.36.0.8 kube03 <none> <none>
rook-ceph-osd-prepare-kubeadm-vj4vl 0/1 Completed 0 88m 10.32.0.6 kubeadm <none> <none>
rook-discover-nz6gd 1/1 Running 0 136m 10.36.0.2 kube03 <none> <none>
rook-discover-qnspv 1/1 Running 0 136m 10.44.0.1 kube02 <none> <none>
rook-discover-r89xz 1/1 Running 0 92m 10.32.0.4 kubeadm <none> <none>
关于kubernetes存储这一块后面将详细介绍。
创建第一个容器化应用
Kubernetes 跟 Docker 等很多项目最大的不同,就在于它不推荐你使用命令行的方式直接运行容器(虽然 Kubernetes 项目也支持这种方式,比如:kubectl run),而是希望你用 YAML 文件的方式,即:把容器的定义、参数、配置,统统记录在一个 YAML 文件中,然后用这样一句指令把它运行起来:
$ kubectl create -f 我的配置文件
跑nignx pod,例:
[root@kubeadm ~]# cat nginx.yaml
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment #Deployment定义多副本应用的对象,还负责在 Pod 定义发生变化时,对每个副本进行滚动更新
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2 #Pod 副本个数 (spec.replicas) 是:2,集群内永远保持有2个副本
template: #定义了一个pod模板,这个模板里面只有一个容器,这个容器镜像是nginx1.18
metadata: #API 对象的“标识”,即元数据,也是我们从 Kubernetes 里找到这个对象的主要依据
labels: #可以通过这个 Labels 字段从 Kubernetes 中过滤出它所关心的被控制对象。
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.18.0
ports:
- containerPort: 80
注:在上面这个 YAML 文件中,Deployment 会把所有正在运行的、携带“app: nginx”标签的 Pod 识别为被管理的对象,并确保这些 Pod 的总数严格等于两个。而这个过滤规则的定义,是在 Deployment 的“spec.selector.matchLabels”字段。我们一般称之为:Label Selector。一个 Kubernetes 的 API 对象的定义,大多可以分为 Metadata 和 Spec 两个部分。前者存放的是这个对象的元数据,对所有 API 对象来说,这一部分的字段和格式基本上是一样的;而后者存放的,则是属于这个对象独有的定义,用来描述它所要表达的功能
查看这个yaml运行起来的状态是否与预期的一致:
[root@kubeadm ~]# kubectl get pods -l app=nginx
NAME READY STATUS RESTARTS AGE
nginx-deployment-75ddd4d4b4-mdn6f 1/1 Running 0 17m
nginx-deployment-75ddd4d4b4-tnk6v 1/1 Running 0 17m
还可以使用 kubectl describe 命令,查看一个 API 对象的细节,比如
[root@kubeadm ~]# kubectl describe pod nginx-deployment-75ddd4d4b4-mdn6f
Name: nginx-deployment-75ddd4d4b4-mdn6f
...
...
...
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-wzkjc (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
default-token-wzkjc:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-wzkjc
Optional: false
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s
node.kubernetes.io/unreachable:NoExecute for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 24m default-scheduler Successfully assigned default/nginx-deployment-75ddd4d4b4-mdn6f to kube03
Normal Pulling 24m kubelet, kube03 Pulling image "nginx:1.18.0"
Normal Pulled 22m kubelet, kube03 Successfully pulled image "nginx:1.18.0"
Normal Created 22m kubelet, kube03 Created container nginx
Normal Started 22m kubelet, kube03 Started container nginx
如果我们要对这个 Nginx 服务进行升级,把它的镜像版本从 1.18 升级为 1.19,要怎么做呢?
只需要更改一下yaml文件中容器的版本:
[root@kubeadm ~]# vi nginx.yaml
...
spec:
containers:
- name: nginx
image: nginx:1.19.0 # Update the version of nginx from 1.14.2 to 1.16.1
ports:
- containerPort: 80
更新:
[root@kubeadm ~]# kubectl apply -f nginx.yaml
注:作为用户,你不必关心当前的操作是创建,还是更新,你执行的命令始终是 kubectl apply,而 Kubernetes 则会根据 YAML 文件的内容变化,自动进行具体的处理。
在这个 nginx Deployment 中声明一个 Volume
[root@kubeadm ~]# vi nginx.yaml
...
spec:
containers:
- name: nginx
image: nginx:1.19.0 # Update the version of nginx from 1.14.2 to 1.16.1
ports:
- containerPort: 80
volumeMounts:
- mountPath: "/usr/share/nginx/html" #容器上要被挂载的目录
name: nginx-vol
volumes:
- name: nginx-vol
# emptyDir: {} #隐式挂载,在宿主机上创建一个临时目录,这个目录将来就会被绑定挂载到容器所声明的Volume 目录上。
hostPath:
path: "/var/data" #挂载到物理主机上的目录
在上述修改完成后,我们还是使用 kubectl apply 指令,更新这个 Deployment:
[root@kubeadm ~]# kubectl apply -f nginx.yaml
可以通过 kubectl get 指令,查看两个 Pod 被逐一更新的过程:
[root@kubeadm ~]# kubectl get pods -l app=nginx
NAME READY STATUS RESTARTS AGE
nginx-deployment-5b5bc57478-frdqt 1/1 Running 0 3s
nginx-deployment-5b5bc57478-hwxkq 1/1 Running 0 9s
nginx-deployment-7b446869f-98rjb 0/1 Terminating 0 14m
nginx-deployment-7b446869f-k224v 0/1 Terminating 0 15m
[root@kubeadm ~]# kubectl get pods -l app=nginx
NAME READY STATUS RESTARTS AGE
nginx-deployment-5b5bc57478-frdqt 1/1 Running 0 3m35s
nginx-deployment-5b5bc57478-hwxkq 1/1 Running 0 3m41s
以使用 kubectl exec 指令,进入到这个 Pod 当中(即容器的 Namespace 中)查看这个 Volume 目录:
[root@kubeadm ~]# touch /var/data/test.log #在这个目录上创建一个文件,观察容器被挂载目录内容变化
[root@kubeadm ~]# kubectl exec -it nginx-deployment-5b5bc57478-hwxkq -- /bin/bash
root@nginx-deployment-5b5bc57478-hwxkq:/# ls /usr/share/nginx/html/
test.log
root@nginx-deployment-5b5bc57478-hwxkq:/# exit
从 Kubernetes 集群中删除yaml文件所建的pod
kubectl delete -f nginx.yaml
给节点打标签
kubectl label nodes kubeadm node=kubeadm
kubectl get node --show-labels