【Kubernetes】K8s笔记(十二):中级篇实战和小结
Kubernetes 技术要点回顾
Kubernetes 是云原生时代的操作系统,它能够管理大量节点构成的集群,让计算资源“池化”,从而能够自动地调度运维各种形式的应用。
使用 kubeadm 搭建集群
【Kubernetes】K8s笔记(七):中级篇 - 搭建多节点实验环境
搭建多节点的 Kubernetes 集群是一件颇具挑战性的工作,好在社区里及时出现了 kubeadm
这样的工具。
kubeadm
使用容器技术封装了 Kubernetes 组件,所以只要节点上安装了容器运行时(cri-o
containerd
等),它就可以自动从网上拉取镜像,然后以容器的方式运行组件,非常简单方便。
配置 Control-Plane
$ sudo kubeadm init \
--apiserver-advertise-address=<host_ip> \
--image-repository registry.aliyuncs.com/google_containers \
--kubernetes-version=<version>\
--pod-network-cidr=10.10.0.0/16
配置 Worker
$ sudo kubeadm join 192.168.165.133:6443 --token <token> \
--discovery-token-ca-cert-hash <hash>
Deployment API 对象
【Kubernetes】K8s笔记(八):Deployment 发布无状态的应用
Deployment 是用来管理 Pod 的一种对象,它代表了运维工作中最常见的一类在线业务,在集群中部署应用的多个实例,而且可以很容易地增加或者减少实例数量,从容应对流量压力。
Deployment 的定义里有两个关键字段:一个是 replicas
,它指定了实例的数量;另一个是 selector
,它的作用是使用标签“筛选”出被 Deployment 管理的 Pod,这是一种非常灵活的关联机制,实现了 API 对象之间的松耦合。
DaemonSet API 对象
【Kubernetes】K8s笔记(九):DaemonSet 守护进程集
DaemonSet 是另一种部署在线业务的方式,它很类似 Deployment,但会在集群里的每一个节点上运行一个 Pod 实例,类似 Linux 系统里的“守护进程”,适合日志、监控等类型的应用。
DaemonSet 能够任意部署 Pod 的关键概念是“污点”(taint)和“容忍度”(toleration)。Node 会有各种“污点”,而 Pod 可以使用“容忍度”来忽略“污点”,合理使用这两个概念就可以调整 Pod 在集群里的部署策略。
Service API 对象
【Kubernetes】K8s笔记(十):Service 解决服务发现的关键问题
Service 是对 Pod IP 地址的抽象,它拥有一个固定的 IP 地址,再使用 iptables 规则把流量负载均衡到后面的 Pod,节点上的 kube-proxy 组件会实时维护被代理的 Pod 状态,保证 Service 只会转发给健康的 Pod。Service 还基于 DNS 插件支持域名,所以客户端就不再需要关心 Pod 的具体情况,只要通过 Service 这个稳定的中间层,就能够访问到 Pod 提供的服务。
Ingress / Ingress Class API 对象和 Ingress Controller 组件
【Kubernetes】K8s笔记(十一):Ingress 集群进出流量总管
Ingress 定义了基于 HTTP 协议的路由规则。
Ingress Controller 是真正的集群入口,控制集群南北向流量,应用 Ingress 规则调度、分发流量,此外还能够扮演反向代理的角色,提供安全防护、TLS 卸载等更多功能。
Ingress Class 是用来管理 Ingress 和 Ingress Controller 的概念,方便我们分组路由规则,降低维护成本。
不过 Ingress Controller 本身也是一个 Pod,想要把服务暴露到集群外部还是要依靠 Service。Service 支持 NodePort、LoadBalancer 等方式,但 NodePort 的端口范围有限,LoadBalancer 又依赖于云服务厂商,都不是很灵活。折中的办法是用少量 NodePort 暴露 Ingress Controller,用 Ingress 路由到内部服务,外部再用反向代理或者 LoadBalancer 把流量引进来。
一个好消息是 Kubernetes 正在进行 Gateway API 对象的开发,现在它已经进入 Beta 阶段。
Gateway API 是以 Gateway 资源(代表底层网络网关/代理服务器)为中心的资源集合, Kubernetes 服务网络的健壮性得益于众多供应商实现、得到广泛行业支持且极具表达力、可扩展和面向角色的各个接口。
Gateway API 最初被认为是知名 Ingress API 的继任者, Gateway API 的好处包括(但不限于)对许多常用网络协议的显式支持 (例如 HTTP、TLS、TCP 、UDP) 以及对传输层安全 (TLS) 的紧密集成支持。 特别是 Gateway 资源能够实现作为 Kubernetes API 来管理网络网关的生命周期。
实战架构
这次部署 WordPress,所有的相关应用都运行在 Kubernetes 集群之中,部署方式都是 Deployment。之前的 Nginx 换成了 Nginx Ingress Controller。WordPress 现在有了多个实例。MariaDB 因为要保证数据一致性暂时还只有一个实例,当然后面我们还可以使用 TiDB 这类分布式数据库。
因为 Kubernetes 内置了服务发现机制 Service,我们再也不需要去手动查看 Pod 的 IP 地址了,只要为它们定义 Service 对象,然后使用域名就可以访问 MariaDB、WordPress 这些服务。
网站对外提供服务的两种方式:
-
让 WordPress 的 Service 对象以 NodePort 的方式直接对外暴露端口 30088,方便测试
-
给 Nginx Ingress Controller 添加
hostNetwork
属性,直接使用节点上的端口号,类似 Docker 的 host 网络模式,好处是可以避开 NodePort 的端口范围限制
1. 部署 MariaDB
首先使用一个 ConfigMap 对象定义数据库的环境变量:
# mariadb/mariadb-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: maria-cm
data:
DATABASE: 'db'
USER: 'wp'
PASSWORD: '123'
ROOT_PASSWORD: '123'
然后我们描述一个 Deployment 来描述 MariaDB 部署后期望的状态,这里 replicas
设置为 1,然后使用 envFrom
把配置信息以环境变量的方式注入 Pod:
# mariadb/mariadb-dep.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: maria-dep
name: maria-dep
spec:
replicas: 1
selector:
matchLabels:
app: maria-dep
template:
metadata:
labels:
app: maria-dep
spec:
containers:
- image: mariadb:10
name: mariadb
ports:
- containerPort: 3306
envFrom:
- prefix: 'MARIADB_'
configMapRef:
name: maria-cm
还需要再为 MariaDB 定义一个 Service 对象,映射端口 3306,让其他应用不再关心 IP 地址,直接用 Service 对象的名字来访问数据库服务:
# mariadb/mariadb_svc.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: maria-dep
name: maria-svc
spec:
ports:
- port: 3306
protocol: TCP
targetPort: 3306
selector:
app: maria-dep
当然,我们也可以将这三个对象放到同一个 YAML 文件中,对象之间使用 ---
隔开,这样就可以一次性创建好:
# mariadb/mariadb-all.yaml
# mariadb ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: maria-cm
data:
DATABASE: 'db'
USER: 'wp'
PASSWORD: '123'
ROOT_PASSWORD: '123'
---
# mariadb Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: maria-dep
name: maria-dep
spec:
replicas: 1
selector:
matchLabels:
app: maria-dep
template:
metadata:
labels:
app: maria-dep
spec:
containers:
- image: mariadb:10
name: mariadb
ports:
- containerPort: 3306
envFrom:
- prefix: 'MARIADB_'
configMapRef:
name: maria-cm
---
# mariadb Service
apiVersion: v1
kind: Service
metadata:
labels:
app: maria-dep
name: maria-svc
spec:
ports:
- port: 3306
protocol: TCP
targetPort: 3306
selector:
app: maria-dep
然后创建这些对象:
$ kubectl apply -f mariadb-all.yaml
configmap/maria-cm created
deployment.apps/maria-dep created
service/maria-svc created
最后检查一下对象的状态:
$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
maria-dep 1/1 1 1 19m
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
maria-dep-658f54c96-9zrsw 1/1 Running 0 20m 10.10.1.58 worker1 <none> <none>
$ kubectl get svc -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 5d22h <none>
maria-svc ClusterIP 10.104.144.64 <none> 3306/TCP 21m app=maria-dep
2. 部署 WordPress
因为刚才创建了 MariaDB 的 Service,所以在写 ConfigMap 配置的时候 HOST
就不应该是 IP 地址了,而应该是 DNS 域名,也就是 Service 的名字 maria-svc
:
# wordpress/wp-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: wp-cm
data:
HOST: 'maria-svc'
USER: 'wp'
PASSWORD: '123'
NAME: 'db'
编写 WordPress 的 Deployment YAML,replicas
设置 3 个,然后用 envFrom
设置环境变量:
# wordpress/wp-dep.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: wp-dep
name: wp-dep
spec:
replicas: 3
selector:
matchLabels:
app: wp-dep
template:
metadata:
labels:
app: wp-dep
spec:
containers:
- image: wordpress:5
name: wordpress
ports:
- containerPort: 80
envFrom:
- prefix: 'WORDPRESS_DB_'
configMapRef:
name: wp-cm
此时仍然要为 WordPress 创建 Service 对象,这里使用了 NodePort
类型,并且手工指定了端口号 30088
(必须在 30000~32767 之间):
# wordpress/wp-svc.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: wp-dep
name: wp-svc
spec:
ports:
- name: http80
port: 80
protocol: TCP
targetPort: 80
nodePort: 30088
selector:
app: wp-dep
type: NodePort
我们还是可以使用 ---
把它们合并为一个文件:
# wordpress/wp-all.yaml
# wp-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: wp-cm
data:
HOST: 'maria-svc'
USER: 'wp'
PASSWORD: '123'
NAME: 'db'
---
# wp-dep.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: wp-dep
name: wp-dep
spec:
replicas: 3
selector:
matchLabels:
app: wp-dep
template:
metadata:
labels:
app: wp-dep
spec:
containers:
- image: wordpress:5
name: wordpress
ports:
- containerPort: 80
envFrom:
- prefix: 'WORDPRESS_DB_'
configMapRef:
name: wp-cm
---
# wp-svc.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: wp-dep
name: wp-svc
spec:
ports:
- name: http80
port: 80
protocol: TCP
targetPort: 80
nodePort: 30088
selector:
app: wp-dep
type: NodePort
现在创建 WordPress 相关的对象:
$ kubectl apply -f wp-all.yaml
configmap/wp-cm created
deployment.apps/wp-dep created
service/wp-svc created
然后检查一下创建的所有对象:
$ kubectl get deploy -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
maria-dep 1/1 1 1 23m mariadb mariadb:10 app=maria-dep
wp-dep 3/3 3 3 4m46s wordpress wordpress:5 app=wp-dep
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
maria-dep-658f54c96-9zrsw 1/1 Running 0 23m 10.10.1.58 worker1 <none> <none>
wp-dep-78647ffc4d-kz956 1/1 Running 0 5m13s 10.10.1.61 worker1 <none> <none>
wp-dep-78647ffc4d-mz6x9 1/1 Running 0 5m13s 10.10.1.59 worker1 <none> <none>
wp-dep-78647ffc4d-xchdz 1/1 Running 0 5m13s 10.10.1.60 worker1 <none> <none>
$ kubectl get svc -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 5d22h <none>
maria-svc ClusterIP 10.104.144.64 <none> 3306/TCP 23m app=maria-dep
wp-svc NodePort 10.100.17.219 <none> 80:30088/TCP 5m27s app=wp-dep
因为 WordPress 的 Service 对象是 NodePort 类型的,我们可以在集群的每个节点上访问 WordPress 服务。
查看节点 IP:
$ kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-master Ready control-plane 5d22h v1.25.2 172.16.63.128 <none> Ubuntu 22.04.1 LTS 5.15.0-52-generic containerd://1.5.9-0ubuntu3
worker1 Ready <none> 5d22h v1.25.2 172.16.63.129 <none> Ubuntu 22.04.1 LTS 5.15.0-52-generic containerd://1.5.9-0ubuntu3
根据节点 IP 访问 30088 端口上的 WordPress:
3. 部署 Nginx Ingress Controller
首先定义 Ingress Class,名字就叫 wp-ink
:
# wp-ingress/wp-ink.yaml
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: wp-ink
spec:
controller: nginx.org/ingress-controller
然后用 kubectl create
命令生成 Ingress 的样板文件,指定域名是 wp.test
,后端 Service 是 wp-svc:80
,Ingress Class 就是刚定义的 wp-ink
:
$ export out="--dry-run=client -o yaml"
$ kubectl create ing wp-ing --rule="wp.test/=wp-svc:80" --class=wp-ink $out
下面是得到的 Ingress YAML,注意路径匹配类型是 Prefix
:
# wp-ingress/wp-ing.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: wp-ing
spec:
ingressClassName: wp-ink
rules:
- host: wp.test
http:
paths:
- backend:
service:
name: wp-svc
port:
number: 80
path: /
pathType: Prefix
编写 Ingress Controller 对象的 YAML,它仍然需要从 Nginx 项目的示例 YAML 修改而来,要改动名字、标签,还有参数里的 Ingress Class:
*kic for kubernetes ingress contrller
# wp-ingress/wp-kic.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: wp-kic-dep
namespace: nginx-ingress
spec:
replicas: 1
selector:
matchLabels:
app: wp-kic-dep
template:
metadata:
labels:
app: wp-kic-dep
spec:
serviceAccountName: nginx-ingress
# use host network
hostNetwork: true
automountServiceAccountToken: true
containers:
- image: nginx/nginx-ingress:2.2-alpine
imagePullPolicy: IfNotPresent
name: nginx-ingress
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443
- name: readiness-port
containerPort: 8081
- name: prometheus
containerPort: 9113
readinessProbe:
httpGet:
path: /nginx-ready
port: readiness-port
periodSeconds: 1
resources:
requests:
cpu: "100m"
memory: "128Mi"
securityContext:
allowPrivilegeEscalation: true
runAsUser: 101 #nginx
runAsNonRoot: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
args:
- -nginx-configmaps=$(POD_NAMESPACE)/nginx-config
- -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret
- -ingress-class=wp-ink
这个 Ingress Controller 不使用 Service,而是给它的 Pod 加上一个特殊字段 hostNetwork
,让 Pod 能够使用宿主机的网络,相当于另一种形式的 NodePort。
创建上述对象:
$ kubectl apply -f wp-ink.yaml -f wp-ing.yaml -f wp-kic.yaml
ingressclass.networking.k8s.io/wp-ink created
ingress.networking.k8s.io/wp-ing created
deployment.apps/wp-kic-dep created
检查一下所有对象是否正常运行:
$ kubectl get deploy -n nginx-ingress -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
wp-kic-dep 1/1 1 1 62s nginx-ingress nginx/nginx-ingress:2.2-alpine app=wp-kic-dep
$ kubectl get pods -n nginx-ingress -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
wp-kic-dep-6577bf795f-2zq89 1/1 Running 0 82s 172.16.63.129 worker1 <none> <none>
$ kubectl get ing -o wide
NAME CLASS HOSTS ADDRESS PORTS AGE
wp-ing wp-ink wp.test 80 2m24s
4. 测试
首先向 /etc/hosts
文件中添加一条记录,IP 地址是 nginx-ingress 所在节点的 IP 地址:
172.16.63.129 wp.test
现在浏览器直接访问 wp.test
即可:
总结
这个网站离真正实用还差得比较远,但框架已经很完善了,可以在这个基础上添加其他功能,比如创建证书 Secret、让 Ingress 支持 HTTPS 等等。
另外,对于数据库 MariaDB 来说,虽然 Deployment 在发生故障时能够及时重启 Pod,新 Pod 却不会从旧 Pod 继承数据,之前网站的数据会彻底消失,这个后果是完全不可接受的。所以后面会继续学习持久化存储对象 PersistentVolume,以及有状态的 StatefulSet 等对象。