Kubernetes,也被称为 K8s 或 Kube,是谷歌推出的业界最受欢迎的容器管理/运维工具(容器编排器)。它是一套自动化容器管理/运维的开源平台,包括部署、调度和节点集群的扩展等。
Kubernetes 的详细介绍,请参考 "系统架构与设计(6)- Kubernetes(K8s)"。
Kubernetes: https://kubernetes.io/
Kubernetes GitHub: https://github.com/kubernetes
本文在 K8s 集群上部署 Nginx 服务,如何部署一个 K8s 集群可以参考 “Docker基础知识 (19) - Kubernetes(二) | 部署 K8s 集群(一主一从)”。
1. 部署环境
虚拟机: Virtual Box 6.1.30(Windows 版)
操作系统: Linux CentOS 7.9 64位
Docker 版本:20.10.7
Docker Compose 版本:2.6.1
Kubernetes 版本:1.23.0
工作目录:/home/k8s
Linux 用户:非 root 权限用户 (用户名自定义,这里以 xxx 表示),属于 docker 用户组
1) 主机列表
主机名 IP 角色 操作系统
k8s-master 192.168.0.10 master CentOS 7.9
k8s-node01 192.168.0.11 node CentOS 7.9
2) 部署和调度
部署的工作都是在 master (192.168.0.10) 主机上进行,K8s 会自动调度到 node 节点。
默认情况下,master 节点不参与调度,且在 master 节点上有一个污点 NoSchedule(表示 K8s 将不会将 Pod 调度到具有该污点的 Node 上),即如果没有 node 节点处于工作状态时,新部署的 Pod 将一直处于 Pending 状态。
让 master 节点参与调度的步骤:
# 查看 node
$ kubectl get nodes
# 查看污点(taints)
$ kubectl describe node k8s-master | grep Taints
Taints: node-role.kubernetes.io/master:NoSchedule
# 删除污点
$ kubectl taint nodes k8s-master node-role.kubernetes.io/master-
# 把 master 标记为 worker。如果想删除标记,运行命令时把 = 换成 -
$ kubectl label nodes k8s-master node-role.kubernetes.io/worker=
给 master 节点添加污点(master 节点不参与调度),运行如下命令:
$ kubectl taint nodes k8s-master node-role.kubernetes.io/master:NoSchedule
3) 工作目录结构
以下目录结构处于 master 节点 /home/k8s 目录下:
k8s
|- init.default.yaml
|- kube-flannel.yml
|- nginx-test
|- namespace.yaml
|- nginx-deployment.yaml
|- nginx-service.yaml
|- nginx
|- conf.d
| |- nginx.conf
|
|- html
| |- test.html
|
|- log
2. 创建命名空间 (namespace)
1) 命名空间 (namespace)
Kubernetes 支持多个虚拟集群,它们底层依赖于同一个物理集群,这些虚拟集群被称为命名空间。
命名空间 (namespace) 是 k8s 集群级别的资源,可以给不同的用户、租户、环境或项目创建对应的命名空间。
命名空间适用于存在很多跨多个团队或项目的用户的场景,对于只有少数项目人员的集群则不需要使用 namespace。
在创建 pod 时可以指定 pod 到 namespace,如果没有指定 namespace,则会默认使用 default 这个 namespace。
2) 创建 namespace.yaml 文件
$ cd /home/k8s/nginx-test # 手动创建 nginx-test 目录
$ vim namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: nginx-test
labels:
name: nginx-test
配置说明:
kind:Namespace 表示该 yaml 文件要创建命名空间;
metadata: 表示命名空间的元信息;
metadata.name: 是命名空间的名称;
metadata.labels: 是命名空间的标签;
3) 执行创建命令
$ kubectl apply -f namespace.yaml
namespace/nginx-test created
# 查看命名空间列表
$ kubectl get namespaces
NAME STATUS AGE
default Active 3h5m
kube-node-lease Active 3h5m
kube-public Active 3h5m
kube-system Active 3h5m
nginx-test Active 23s
# 查看命名空间详情
$ kubectl describe namespace nginx-test
Name: nginx-test
Labels: kubernetes.io/metadata.name=nginx-test
name=nginx-test
Annotations: <none>
Status: Active
No resource quota.
No LimitRange resource.
3. 创建 Deployment
1) Deployment 简介
Deployment 是一个定义及管理多副本应用(即多个副本 Pod)的新一代对象,与 Replication Controller 相比,它提供了更加完善的功能,使用起来更加简单方便。
在 k8s 中 Deployment 对象管理 Pod 对象的方法,被叫作 "控制器" 模式(controller pattern),Deployment 扮演的是 Pod 的 "控制器" 角色。
Deployment 通过管理 ReplicaSet 来间接管理 Pod,即:Deployment 管理 ReplicaSet,ReplicaSet 管理 Pod。所以 Deployment 比 ReplicaSet 的功能更强大。
如果 Pod 出现故障,对应的服务也会挂掉,所以 Kubernetes 提供了一个 Deployment 的概念 ,目的是让 Kubernetes 去管理一组 Pod 的副本,也就是副本集,这样就能够保证一定数量的副本一直可用,不会因为某一个 Pod 挂掉导致整个服务挂掉。
Deployment 还负责在 Pod 定义发生变化时,对每个副本进行滚动更新(Rolling Update)。
2) 创建 nginx.conf 文件
$ cd /home/k8s/nginx-test/nginx/conf.d # 手动创建 nginx/conf.d 各级子目录
$ vim nginx.conf
server {
listen 80 default_server;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.php index.html index.htm;
autoindex off;
}
}
3) 创建 test.html 文件
$ cd /home/k8s/nginx-test/nginx/html # 手动创建 nginx/html 各级子目录
$ vim test.html
<p>K8s Nginx - HTML page</p>
4) 创建 nginx-deployment.yaml 文件
$ cd /home/k8s/nginx-test
$ vim nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: nginx-test
spec:
replicas: 1
selector:
matchLabels:
app: nginx-pod
template:
metadata:
labels:
app: nginx-pod
spec:
s:
- name: nginx-1-21
image: nginx:1.21
ports:
-Port: 80
volumeMounts:
- name: conf
mountPath: /etc/nginx/conf.d
- name: log
mountPath: /var/log/nginx
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: conf
hostPath:
path: /home/k8s/nginx-test/nginx/conf.d
type: Directory
- name: log
hostPath:
path: /home/k8s/nginx-test/nginx/log
type: Directory
- name: html
hostPath:
path: /home/k8s/nginx-test/nginx/html
type: Directory
配置说明:
kind: Deployment 表示该 yaml 文件要创建 Deployment 发布;
metadata: 表示这个 deployment 的元信息;
metadata.name: 是 deployment 的名称 nginx-deployment;
metadata.labels: 是 deployment 的标签;
metadata.namespace: 是 deployment 所在的命名空间;
spec: 表示 deployment 的详细参数配置说明;
spec.replicas: 是启动几个 pod 节点(副本);
spec.template.spec: 是 deployment 选择模块的详细说明;
spec.template.spec.containers:表示容器,此处是 nginx 的 docker 镜像,容器的端口设置Port: 80, volumeMounts 表示绑定的文件和目录;
spec.template.spec.volumes:表示选择的容器挂载的宿主机的文件和目录 conf, logs 和 html;
注:手动创建 /home/k8s/nginx-test/nginx/log 目录。
# 执行创建命令
$ kubectl apply -f nginx-deployment.yaml
deployment.apps/nginx-deployment created
# 查询 pods
$ kubectl get pods -n nginx-test -o wide
NAME READY STATUS ... AGE IP NODE
nginx-deployment-56786d8495-rxr9c 1/1 Running 3m32s 10.244.0.10 k8s-master
# 集群内访问 nginx(Pod 的虚拟 IP,外部无法访问)
$ curl http://10.244.0.10/test.html
<p>K8s Nginx - HTML page</p>
5) 操作 Deployment
# 查询命名空间 nginx-test 下的 deployment
$ kubectl get deploy -n nginx-test
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 1/1 1 1 144m
# 查询 deployment 详情
$ kubectl describe deploy nginx-deployment -n nginx-test
Name: nginx-deployment
Namespace: nginx-test
CreationTimestamp: Wed, 16 Nov 2022 17:44:39 -0500
Labels: <none>
Annotations: deployment.kubernetes.io/revision: 1
Selector: app=nginx-pod
Replicas: 1 desired | 1 updated | 1 total | 1 available | 0 unavailable
...
# 动态修改 deployment 的副本数量
$ kubectl scale deploy nginx-deployment --replicas=2 -n nginx-test
deployment.apps/nginx-deployment scaled
# 重启 deployment
$ kubectl rollout restart deploy nginx-deployment -n nginx-test
deployment.apps/nginx-deployment restarted
# 删除一个 deployment
$ kubectl delete deploy nginx-deployment -n nginx-test
6) 操作 ReplicaSet
# 查询命名空间 nginx-test 下的 ReplicaSet
$ kubectl get rs -n nginx-test
NAME DESIRED CURRENT READY AGE
nginx-deployment-fc98c7b77 2 2 0 34m
7) 操作 Pod
# 查询命名空间 nginx-test 下的 pod
$ kubectl get pods -n nginx-test
NAME READY STATUS RESTARTS AGE
nginx-deployment-6487bc588b-nnpw8 1/1 Running 0 5m10s
nginx-deployment-6487bc588b-sfxh9 1/1 Running 0 5m12s
# 查询 pod 详情
$ kubectl describe pod nginx-deployment-6487bc588b-nnpw8 -n nginx-test
Name: nginx-deployment-6487bc588b-nnpw8
Namespace: nginx-test
Priority: 0
Node: k8s-master/192.168.0.10
Start Time: Wed, 16 Nov 2022 20:14:42 -0500
Labels: app=nginx-pod
pod-template-hash=6487bc588b
...
# 查看 pod 日志
$ kubectl logs nginx-deployment-6487bc588b-nnpw8 -n nginx-test
# 删除 pod
$ kubectl delete pod nginx-deployment-6487bc588b-nnpw8 -n nginx-test
# 强制删除 pod
$ kubectl delete pod nginx-deployment-6487bc588b-nnpw8 -n nginx-test --force --grace-period=0
4. 创建 Service
上文 Deployment 创建的一组 Pod 提供具有高可用性的 Nginx 服务。
虽然每个 Pod 都会分配一个单独的 Pod 虚拟 IP,但有如下两个问题:
(1) Pod 虚拟 IP 会随着 Pod 的重建产生变化;
(2) Pod 虚拟 IP 仅仅是集群内可见,外部无法访问;
kubernetes 设计了 Service 来解决以上问题,Service 可以看作是一组同类 Pod 对外的访问接口,基于 Service,应用可以方便地实现服务发现和负载均衡。
1) 创建 nginx-service.yaml 文件
$ cd /home/k8s/nginx-test
$ vim nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: nginx-pod
name: nginx-service
namespace: nginx-test
spec:
ports:
- port: 9080
name: nginx-service-80
protocol: TCP
targetPort: 80
nodePort: 30080
selector:
app: nginx-pod
type: NodePort
配置说明:
kind: Service 表示 yaml 文件创建的是一个 Service;
metadata: 表示这个 Service 的元信息;
metadata.name: 是 Service 的名称 nginx-service;
metadata.labels: 是 Service 的标签;
metadata.namespace: 是 Service 的命名空间,此处选择的是第一步创建的命名空间 nginx;
sepc: 是 Service 的详细配置说明;
sepc.type: NodePort 表示外部可以访问,ClusterIP 表示集群内访问;
sepc.selector: 表示这个 Service 是将带标签的哪些 pods 做为一个集合对外通过服务;
sepc.ports.port: 是 Service 绑定端口 9080 ;
sepc.ports.name: nginx-service-80 表示 Service 服务的名称;
sepc.ports.protocol: TCP 表示 Service 转发请求到容器的协议是 TCP,我们部署的 http 的 nginx 服务,因此选择协议为TCP;
sepc.ports.targetPort: 80 表示 Service 转发外部请求到容器的目标端口 80,即 deployment 的 pod 容器对外开放的容器端口 80;
sepc.ports.nodePort: 30080 表示 Service 对外开放的节点端口;
# 执行创建 Service 命令
$ kubectl apply -f nginx-service.yaml
# 查询服务列表
$ kubectl get services -n nginx-test
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service NodePort 10.104.204.25 <none> 9080:30080/TCP 16s
浏览器外部访问 http://192.168.0.10:30080/test.html,显示结果如下:
K8s Nginx - HTML page
2) 操作 Service
# 查询 Service 详情
$ kubectl describe service nginx-service -n nginx-test
Name: nginx-service
Namespace: nginx-test
Labels: app=nginx-pod
Annotations: <none>
Selector: app=nginx-pod
Type: NodePort
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.104.204.25
IPs: 10.104.204.25
Port: nginx-service-80 9080/TCP
TargetPort: 80/TCP
NodePort: nginx-service-80 30080/TCP
Endpoints: 10.244.0.12:80,10.244.0.13:80
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
# 删除 service 服务
$ kubectl delete services nginx-service -n nginx-test
service "nginx-service" deleted
5. 维护 node 节点
K8s v1.2 加入了 cordon、drain、uncordon 三个命令用户实现节点的维护,有了这三个命令,在维护 node 节点时,就不必把要维护的 node 节点退出集群。维护 node 节点的步骤如下:
# 查看节点
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master Ready control-plane,master 140m v1.23.0
k8s-node01 Ready <none> 18m v1.23.0
# 设置 k8s-node01 节点不可调度
$ kubectl cordon k8s-node01
# 驱逐/清空节点上的 Pod
$ kubectl drain k8s-node01 --force --ignore-daemonsets
# 维护工作
...
# 复节点可调度
$ kubectl uncordon k8s-node01
6. 注意要点
以上的部署工作都是在 master (k8s-master,192.168.0.10) 主机上进行,工作目录在 /home/k8s,测试运行 nginx 服务时要注意以下两种情况:
1) master 节点不参与调度
这种情况下 node 节点(k8s-node01,192.168.0.11)必须处于 Ready 状态,运行如下命令查询:
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master Ready control-plane,master 140m v1.23.0
k8s-node01 Ready <none> 18m v1.23.0
当 node 节点处于 Ready 状态时,集群应该会很快把 Pod 调度到 node 节点运行。此时 nginx 无法正常运行,因为 nginx 找不到 /home/k8s/nginx-test/nginx 目录及其子目录 (node 节点上没有这个目录),需要把 master 下的 /home/k8s/nginx-test/nginx 目录完全复制到 node 下的 /home/k8s/nginx-test/nginx 目录。
2) master 节点参与调度
这种情况下如果 node 节点处于 Ready 状态,也需要复制 master 的 /home/k8s/nginx-test/nginx 目录。
如果 node 节点处于 NotReady 状态,可以不复制 master 的 /home/k8s/nginx-test/nginx 目录,因为 Pod 只会在 master 上运行。