Kubernetes(K8s)

建议阅读此向导: hobby-kube/guide
建设K8s集群至少需要3台主机.

  • 设置防火墙
  • 使用WireGuard(目前最好的VPN方案)设置安全的VPN用于VPS之间的网络通讯,
    不依赖于服务器提供商提供的专有网络, 以达到最佳的安全性.

使用Nginx Ingress替代专用负载均衡服务器(这将引入单点故障).

将会使用到kubeadm以快速创建k8s集群.

由rancher维护的轻量级k8s, 经过CNCF认证, 512MB内存就可以运行, 官方建议1G内存.

K3s比K8s轻量得多, 可以通过脚本完成本地安装, 也可以通过K3sup在开启SSH的服务器上远程安装.
用K3d可以在任何有Docker的环境下进行本地开发.

可参考 https://github.com/erebe/personal-server

由ubuntu维护的轻量级k8s, 只能在安装了snap的linux(ubuntu)上运行, 最低需要1G内存, 官方建议4G内存.

单文件k8s, 硬件需求和k3s类似.

在docker上运行k3s集群的帮手工具, 相当于k3s的kind.
主要用于开发, 可在单机上实现k3s集群.

用于开发, 在虚拟机上运行k8s集群.

用于开发和CI, 在docker容器中运行k8s集群.

桌面版Docker自带K8s集群, 只需要在设置里开启即可.

Docker Swarm是专用于Docker的容器编排工具.
Docker Swarm更容易安装.
Docker Swarm的容器部署速度比K8s快.
Docker Swarm被广泛认为正接近生命终结, 缺乏投资价值.

K8s主要是为在云服务平台上运行而存在的, 因此需要依赖于云服务商提供的负载均衡器(通常是硬件).
K8s支持多种容器运行时, 因此使用kubectl替代过于具体的docker cli和docker-compose.
K8s的安装较为困难.
K8s使用自己的YAML格式定义容器编排.
K8s不支持交换内存, 当Pod达到内存限制时, 会被驱逐.
有弹性的K8s集群至少由3台主机组成, 这是因为etcd最低需要3个集群成员才有容错性.

K8s的包(应用程序)管理器, 包被称为Charts(图表).

K8s集群中的节点分为两种类型:

  • 主节点(master): 负责调度整个集群, 运行以下核心组件:
    • API服务器: 接口
    • 调度器(Schduler): 安排容器运行, 管理容器会落在哪个工作节点上.
    • Kube Controller Manager: 监视集群的状态, 协调运行.
    • Cloud Controller Manager:
      1.8加入的alpha特性, 仅当K8s集群在云中运行时才会运行.
      用于与云服务提供商进行通讯, 以达到管理节点, 负载均衡和路由的目的.
    • etcd: 分布式键值存储器, 用于保存集群的状态.
  • 工作节点(worker): 负责运行应用.
    • kublet:
      • 与API服务器通讯
      • 通过CRI(容器运行时接口)管理节点上的容器
    • kube-proxy: 提供网络代理(以支持service)

一个K8s集群至少有一个工作节点和一个主节点.

kubectl config current-contet 查看当前上下文
kubectl config use-context {context_name} 切换到指定上下文
kubectl config get-contexts 获取K8s的上下文列表

K8s提供了图形化界面用于管理K8s集群.

部署K8s Dashboard.
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0/aio/deploy/recommended.yaml

使用此命令获得Token(在我上次使用时, Dashboard无法正常登录)
kubectl describe secret -n kube-system | grep deployment -A 12

K8s使用K8s对象表示整个集群的状态, 并将集群里的节点向对象期望的状态进行变更.
K8s对象的名称在同一类型的资源中具有唯一性.

Pods可以缩写为po, pod
ReplicaSet可以缩写为rs
Deployment可以缩写为deploy
Service可以缩写为svc

可以直接通过命令创建K8s对象, 而不经过规约(spec), 这种方法适用于一次性任务, 不适合用于生产.

创建depolyment对象来运行nginx容器的实例
kubectl run nginx --image nginx
等同于以下语句
kubectl create deployment nginx --image nginx

使用规约(spec, 即 .yaml 文件)来创建对象的实例.

  • 通过规约创建对象
    kubectl create -f nginx.yaml
  • 通过规约删除对象
    kubectl delete -f nginx.yaml -f redis.yaml
  • <<通过规约更新对象>>
    kubectl replace -f nginx.yaml

Pod是一个或多个容器(集装箱)的集合, 具有唯一的IP地址(重新启动Pod会获得不同的IP地址).
Pod内的容器共享相同的网络和存储, 可以通过localhost相互通信.
Pod的IP地址只能在K8s集群(即K8s管理的其他容器)中访问, 在K8s集群以外无法访问.

Pod是一种可缩放的单位, 因此可以从一个Pod缩放至多个Pod, 每个Pod里都运行相同的容器.

通常不直接使用, 而由[[ReplicaSet]]创建和管理.

apiVersion: apps/v1
kind: Pod
metadata:
name: hello-pod
labels:
app: hello
spec:
containers:
- name: hello-container
image: busybox
command: ['sh', '-c', 'echo Hello from my container! && sleep 3600']

kubectl create -f {yaml_filename} 根据yaml文件创建资源(Pod, ReplicaSet)

kubectl get pods 获得默认集群命名空间的Pod列表
kubectl get pods --watch 监视pods变动
kubectl get pods -l={label} 根据labels过滤输出pod列表, 例如 app=hello
kubectl get pods {pod_name} -o=yaml 输出对应pod在运行时的yaml状态信息

kubectl exec {pod_name} env 输出特定Pod的环境变量
kubectl exec -ti {pod_name} bash 打开特定Pod的bash

kubectl delete pod {pod_name} 删除Pod

kubectl logs {pod_name} 根据Pod的名称输出Pod内运行的容器的输出
kubectl logs {pod_name} -c {container_name} 输出Pod内具体容器的输出

从模板里生成Pod, 用于自动维护Pod的副本数量:

  • 如果Pod数量超出replicas值, 则会删除它
  • 如果Pod数量低于replicas值, 则会创建新的Pod
  • 已经启动的ReplicaSet会自动接管符合条件(selector)的Pods.

通常不直接使用, 而由[[Deployment]]创建和管理.

apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: hello
labels:
app: hello
spec:
replicas: 5
selector:
matchLabels:
app: hello
template: # Pod 模板
metadata:
labels:
app: hello
spec:
containers:
- name: hello-container
image: busybox
command: ['sh', '-c', 'echo Hello from my container! && sleep 3600']

kubectl create -f {yaml_filename} 根据yaml文件创建资源(Pod, ReplicaSet)
kubectl delete rs {replicate_name} 删除ReplicateSet
kubectl get replicaset 获得默认集群命名空间的ReplicaSet列表

Deployment是ReplicaSet的包装, 用于对Pod进行受控的更新.

Deployment的YAML定义几乎与ReplicaSet相同:

apiVersion: apps/v1
kind: Deployment
metadata:
name: hello
labels:
app: hello
spec:
replicas: 5
selector:
matchLabels:
app: hello
template:
metadata:
labels:
app: hello
spec:
containers:
- name: hello-container
image: busybox
command: ['sh', '-c', 'echo Hello from my container! && sleep 3600']

命令可以用 {object_kind}/{object_name} 的形式指定对象类型, 免去空格

根据yaml文件创建Deployment, 并开启record标志
kubectl create -f {deployment_yaml_filename} --record

列出所有部署
kubectl get deployments

缩放Deployment里的Pod数量为number
kubectl scale deployment {deployment_name} --replicas={number}

更新正在运行的Depolyment里的容器镜像(不推荐, 应该通过规约更新对象)
kubectl scale image deployment {deployment_name} {位于spec.containers里的container_name}={image} --record

查看对部署作出的修订(需要部署时开启record标志)
kubectl rollout history deployment {deployment_name}

向前回滚一个修订(这会导致一个新的修订, 因此如果再次运行, 则相当于撤销回滚)
kubectl rollout undo deployment {deployment_name}

删除部署
kubectl delete deployment {deployment_name}

该部署策略通过删除旧的Pod, 创建新的Pod实现.
在该策略下, 只有当旧Pod全部终止时, 才会开始创建新的Pod, 因此会造成服务在一段时间内不可用.

...
spec:
...
strategy:
type: Recreate
...
...

该部署策略与Recrate不同, 通过maxUnavailable和maxSurge控制同一时间里最大的不可用Pod数量.
maxUnavaliable, 最大不可用Pod数量, 值可以是百分比或具体数字, 默认值是25%
maxSurge, 超过replicas的最大临时Pod数量, 默认值是25%, 即允许同时存在1.25倍replicas指定数量的Pod.

...
spec:
...
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavaliable: 40%
maxSurge: 40%
...
...

服务用来给部署的Pod设立固定的IP地址和端口映射, 它会将流量转发给Pod.

K8s里的服务会按照此格式获得以下DNS名称:

  • service-name.namespace.svc.cluster-domain.sample
    例如 web-frontend.default.svc.cluster.local
  • service-name
    例如 web-frontend
  • service-name.namespace
    例如 web-frontend.default

服务分为以下类型:

  • ClusterIP(默认): 在集群的内部IP上公开Service.
    这种类型的服务只能在K8s集群内部访问, 如果需要在集群外访问, 则需要Proxy.
  • NodePort: ClusterIP的超集, 通过NAT公开服务, 允许K8s集群以外的流量进入.
    这种类型的服务仍然只有内网IP, 但该内网IP可以在K8s集群外被访问.
    通过将负载均衡器与服务置于同一虚拟网络, 就能将服务暴露给互联网.
    服务会自动得到一个30000~32767的端口号(因此服务端口也只是一个映射端口).
  • LoadBalancer: NodePort的超集, 会通过Cloud Manager自动创建负载均衡器.
  • ExternalName: 返回带有名称的CNAME记录, 将服务映射到DNS名称.
apiVersion: apps/v1
metadata:
name: web-frontend
labels:
app: web-frontend
spec:
selector:
app: web-frontend
ports:
- port: 80 # 服务端口
name: http
targetPort: 3000 # 需要映射的Pod端口

NodePort

...
spec:
type: NodePort
...
...

LoadBalancer

...
spec:
type: LoadBalancer
...
...

ExternalName

...
spec:
type: ExternalName
externalName: mydatabase.example.com
...

kubectl create -f {service_filename} 创建service资源
kubectl get service 列出service信息, 这也是查看服务IP的方法
kubectl delete service {service_name} 删除service资源

Service可以直接在IP上暴露服务, 但这种方式无法保留HTTP请求的源IP地址.

apiVersion: apps/v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: my-app
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
externalIPs:
- 80.11.12.10 # 外部IP地址

Ingress是一种连接起互联网和Service的HTTP负载均衡器, 这种对象需要Ingress控制器才能工作.
Ingress通过配置规则对流量进行路由, 支持根据主机名和路径进行路由.

Ingress控制器是一种安装在K8s里的负载均衡部件(因此不会在spec里指明),
可通过在规约里定义annotations(注解)进行配置.

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: example-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
rules:
- host: hello-world.info
http:
paths:
- path: /
backend:
serviceName: web
servicePort: 8080

代理只用于调试和故障排除, 请不要用向互联网公开.

向K8s外暴露API服务器, 并绑定8080端口.
kubectl proxy --port=8080

在暴露API服务器后, 可以用以下格式访问服务:
http://localhost:[PORT]/api/v1/namespaces/[NAMESPACE]/services/[SERVICE_NAME]:[SERVICE_PORT]/proxy/
例如 =http://localhost:8080/api/v1/namespaces/default/services/web-frontend:80/proxy/+

与Docker持久化在文件系统上的卷不同, K8s的卷生命周期与Pod相同, 因此删除Pod会导致卷一并被删除.

查看特定服务的描述
kubectl describe service {service_name}

在K8s上, 由于Pod是瞬态的(意味着轻易被关闭和创建), 数据库会很难部署.
将应用迁移至K8s意味着将软件系统的范式转变为分布式系统.

注: 像MySQL和PostgreSQL这样的RDBMS不具有K8s友好的数据库模型.

将持久化组件外包给云计算或以传统方式单独维护数据库集群.

一种确保Pod唯一且有序的API对象, 每个Pod都会有一个永久的标识符, 无论如何重新调度, Pod都会保留.
因此在Pod发生故障后, 可以重新连接到PersistentVolume上.

K8s的存储资源, 独立于Pod运行.