CH1 背景回顾:云原生大事记
初出茅庐
2013年的后端技术领域,最如日中天的技术当属于 Paas (Platform-as-a-service, 平台即服务) 项目。以Cloud Foundry 为代表的 Paas 项目备受欢迎的一个主要原因,在于它提供了一种“应用托管”的能力。
当时部署应用的主流做法,是租一批虚拟机,用脚本或者手动的方式在这些机器上部署应用。 但是在部署过程中经常会遇到云端虚拟机和本地环境不一致的问题,所以当时的云计算服务比的就是谁能更好地模拟本地服务器环境,这个问题的最佳解决方案就是 Paas 开源项目。例如,虚拟机创建好之后,开发者只需要一条命令就能把本地应用部署到云上:
cf push <myApplication>
其中最核心的组件,就是一套应用的打包和分发机制。用户把应用的可执行文件和启动脚本打进一个压缩包内,上传到云上Cloud Foundry 的存储中。接着 Cloud Foundry 会通过调度器选择一个可以运行这个应用的虚拟机,然后通知这个机器上的 Agent 下载应用压缩包并启动。
由于需要在一个虚拟机上启动来自多个不同用户的应用,Cloud Foundry 会调用操作系统的 Cgroups 和 Namespace 机制为每一个应用单独创建一个称为“沙盒”的隔离环境,然后在“沙盒”中启动这些应用进程,从而把多个用户的应用互不干涉地在虚拟机里批量地、自动地运行起来。这个隔离环境,实际上就是所谓的“容器”。
然而,就在此时,dotCloud 公司决定将自己的容器项目 Docker 开源,在短短几个月内就迅速崛起,取代了 Cloud Foundry 等一众 Paas 社区。Docker 实际上只是一个同样使用 Cgroups 和 Namespace 实现的“沙盒“,与前面提到的 Paas 没有太大区别,除了一小部分不同的功能—— Docker 镜像。
前面讲到,Paas 之所以能够帮助用户大规模地部署应用到集群中,是因为它提供了一个应用打包的功能,但这也成为了用户不断诟病的一个功能。一方面,对于不同的语言、不同的框架,以及应用更新的不同版本,用户都需要分别单独维护一个打好的包,十分混乱。另一方面,由于本地与服务器环境的差异,将应用部署到云上时,常常要进行许多配置的修改工作,才能正常运行起来。
而 Docker 镜像的创新,从根本上解决了这个问题。所谓Docker镜像,其实也是一个压缩包。但是这个压缩包里的内容,比 PaaS 的应用可执行文件 + 启停脚本的组合要丰富得多。实际上,大多数 Docker 镜像是直接由一个完整操作系统的所有文件和目录构成的,所以这个压缩包里的内容跟你本地开发和测试环境用的操作系统完全一样。
这样,只要拥有了 Docker 镜像,然后用某种技术创建一个“沙盒”,将镜像解压到“沙盒”中,就可以运行你的应用了。更重要的是,这个压缩包包含了完整的操作系统文件和目录,也就是包含了这个应用运行所需要的所有依赖,所以你可以先用这个压缩包在本地进行开发和测试,完成之后再上传到云端运行。在整个过程中,无论是本地还是云端,获得的都是一致的运行环境。
所以,你只需要提供下载好的操作系统文件与目录,然后使用它制作—个压缩包即可:
docker build <myImage>
一旦镜像制作完成,也只需要一行命令就能启动它:
docker run <myImage>
崭露头角
前面讲到,Docker 镜像这个功能的创新,解决了 Paas 中一个棘手的问题:打包应用。一时之间,“容器化”取代“ PaaS 化”成为了基础设施领域最炙手可热的关键词,一个以“容器”为中心的全新的云计算市场正呼之欲出。
此时,Docker 公司又做出了一个重大的决定:发布 Swarm 项目。
虽然 Docker 解决了应用打包的问题,但是归根结底,如何将应用部署到平台上才是最重要的问题,而 Swarm 项目则是为了这个目的。
群雄并起
虽然 Docker 项日备受追捧,但用户最终要部署的还是他们的网站、服务、数据库,甚至是云计算业务。这就意味着,只有那些能够为用户提供平台层能力的工具才会真正成为开发者关心和愿意付费的产品。而 Docker 项目这样一个只能用来创建和启停容器的小工具,最终只能充当这些平台项目的“幂后英雄”。
Swarm 项目作为一个整体对外提供集群管理功能,其最大亮点是它完全使用 Docker 项目原本的容器管理 API 来完成集群管理,比如:
单机 Docker 项目:
docker run <myContainer>
多机 Docker 项目:
docker run -H <Swarm集群地址> <myContainer>
所以,在部署了 Swam 的多机环境中,用户只需要使用原先的 Docker 指令创建—个容器,Swarm 就会拦截这个请求并处理,然后通过具体的调度算法找到一个合适的 Docker Daemon 运行起来。
后来,Docker 又收购了Fig 项目,并改名为 Compose,从而实现了“容器编排”的功能。假如现在用户需要部署的是应用容器A,数据库容器B,负载均衡容器C,那么 Docker Compose 就允许用户把 A、B、C 这 3 个容器定义在一个配置文件中,并且可以指定它们之间的关联关系,比如容器 A 需要访问数据库容器 B。然后使用一行命令即可启动这三个容器:
docker compose up
尘埃落定
就在 Docker 项目如日中天,并对其他公司造成了极大的挑战之时,基础设施领域的翘楚谷歌公司突然发力,正式宣告了 Kubernetes 项目的诞生,再一次改变了整个容器市场的格局。
CH2 容器技术基础
从进程开始说起
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。
容器技术的核心功能,就是通过约束和修改进程的动态表现,为其创造一个“边界”。对于 Docker 等大多数 Linux 容器来说,Cgroups 技术是用来制造约束的主要手段而 Namespace 技术是用来修改进程视图的主要方法。
下面通过实践说明 Cgroups 和 Namespace 这两个概念:
首先创建一个容器:
$ docker run -it busybox /bin/sh
/ #
然后在容器中执行 ps
指令:
/ # ps
PID USER TIME COMMAND
1 root 0:00 /bin/sh
7 root 0:00 ps
最开始执行的 /bin/sh
是这个容器内部的第 1 号进程,并且共有两个进程在运行。这就意味着前面执行的 /bin/sh
以及刚刚执行的 ps
,已经被 Docker 隔离在了一个跟宿主机完全不同的世界当中。
正常情况下,在宿主机上运行了一个 /bin/sh
程序时,操作系统会给它分配一个 PID,比如 PID=100。现在,通过 Docker 在一个容器中运行 /bin/sh
时,会通过 Linux 中的 Namespace 机制,对被隔离应用的进程空间做手脚,使得这些进程只能“看到”重新计算过的 PID,比如上面的 PID=1,实际上,在宿主机的操作系统中,还是 PID=100 的进程。
除了 PID Namespace,Linux 还提供了 Mount、UTS、 IPC、NetWork 和 User 这些 Namespace,用来对各种进程上下文进行处理。
隔离与限制
虽然容器内的第 1 号进程只能“看到”容器里的情况,但是在宿主机上,它作为第 100 号进程与其他所有进程之间依然是平等的竞争关系。这就意味看虽然第100号进程表面上被隔离了起来,但是它所能够使用到的资源(比如CPU、内存),可以随时被宿主机上的其他进程(或者其他容器)占用。当然,这个第 100 号进程自己也可能用光所有资源。这些情况显然都不是—个“沙盒”应该表现出来的合理行为。
Linux Cgroups(Linux control groups)就是 Linux 内核中用来为进程设置资源限制的一个重要功能。Linux Cgroups 最主要的作用就是限制一个进程组能够使用的资源上限,包括CPU、内存、磁盘、网络带宽等等。此外,Cgroups 还能够对进程进行优先级设置、审计,以及将进进程挂起和恢复等操作。
在 Linux 中,Cgroups 以文件和目录的方式组织在操作系统的 /sys/fs/cgroup
路径:
$ mount -t cgroup2
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)
$ ls /sys/fs/cgroup
cgroup.controllers cpu.stat memory.pressure
cgroup.max.depth dev-hugepages.mount memory.reclaim
cgroup.max.descendants dev-mqueue.mount memory.stat
cgroup.pressure init.scope misc.capacity
cgroup.procs io.cost.model sys-fs-fuse-connections.mount
cgroup.stat io.cost.qos sys-kernel-config.mount
cgroup.subtree_control io.pressure sys-kernel-debug.mount
cgroup.threads io.prio.class sys-kernel-tracing.mount
cpu.pressure io.stat system.slice
cpuset.cpus.effective irq.pressure user.slice
cpuset.mems.effective memory.numa_stat
如何使用 Cgroup 的配置呢?首先需要创建一个“控制组”:
$ mkdir container
$ ls container
cgroup.controllers hugetlb.1GB.current memory.high
cgroup.events hugetlb.1GB.events memory.low
cgroup.freeze hugetlb.1GB.events.local memory.max
cgroup.kill hugetlb.1GB.max memory.min
cgroup.max.depth hugetlb.1GB.numa_stat memory.numa_stat
cgroup.max.descendants hugetlb.1GB.rsvd.current memory.oom.group
cgroup.pressure hugetlb.1GB.rsvd.max memory.peak
cgroup.procs hugetlb.2MB.current memory.pressure
cgroup.stat hugetlb.2MB.events memory.reclaim
cgroup.subtree_control hugetlb.2MB.events.local memory.stat
cgroup.threads hugetlb.2MB.max memory.swap.current
cgroup.type hugetlb.2MB.numa_stat memory.swap.events
cpu.idle hugetlb.2MB.rsvd.current memory.swap.high
cpu.max hugetlb.2MB.rsvd.max memory.swap.max
cpu.max.burst io.bfq.weight memory.zswap.current
cpu.pressure io.latency memory.zswap.max
cpuset.cpus io.low misc.current
cpuset.cpus.effective io.max misc.events
cpuset.cpus.partition io.pressure misc.max
cpuset.mems io.prio.class pids.current
cpuset.mems.effective io.stat pids.events
cpu.stat io.weight pids.max
cpu.uclamp.max irq.pressure pids.peak
cpu.uclamp.min memory.current rdma.current
cpu.weight memory.events rdma.max
cpu.weight.nice memory.events.local
现在以修改 CPU 配额为例:
$ echo 20000 100000 > /sys/fs/cgroup/container/cpu.max
$ cat while.sh
while :
do
:
done
$ sh ./while.sh &
[1] 67372
$ top
top - 15:10:29 up 20:17, 2 users, load average: 1.62, 1.11, 1.32
任务: 402 total, 2 running, 400 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.2 us, 0.2 sy, 6.3 ni, 92.7 id, 0.1 wa, 0.4 hi, 0.1 si, 0.0 st
MiB Mem : 15721.4 total, 4781.3 free, 6629.8 used, 6106.2 buff/cache
MiB Swap: 16384.0 total, 14614.0 free, 1770.0 used. 9091.6 avail Mem
进程号 USER PR NI VIRT RES SHR %CPU %MEM TIME+ COMMAND
67372 kkkstra 25 5 10612 2956 2624 R 100.0 0.0 0:36.24 sh
...
$ echo 67372 > /sys/fs/cgroup/container/cgroup.procs
$ top
top - 15:11:56 up 20:19, 2 users, load average: 1.20, 1.09, 1.29
任务: 402 total, 2 running, 400 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.9 us, 0.5 sy, 1.5 ni, 96.7 id, 0.0 wa, 0.3 hi, 0.1 si, 0.0 st
MiB Mem : 15721.4 total, 4704.9 free, 6664.1 used, 6143.7 buff/cache
MiB Swap: 16384.0 total, 14616.0 free, 1768.0 used. 9057.3 avail Mem
进程号 USER PR NI VIRT RES SHR %CPU %MEM TIME+ COMMAND
67372 kkkstra 25 5 10612 2956 2624 R 20.0 0.0 1:56.83 sh
...
CH3 Kubernetes设计与架构
CH4 Kubernetes集群搭建与配置
1. Kubeadm
工作原理
直接在宿主机上运行 kubelet,然后使用容器部署其他 Kubernetes 组件。
kube init 的工作流程
- Preflight Checks,在执行
kubeadm init
指令后 kubeadm 首先要做一系列检查工作,以确定这台机器可以用来部署 Kubemetes。 - 生成 Kubernetes 对外提供服务所需的各种证书和对应目录。kubeadm为 Kubernetes 项目生成的证书文件都放在 Master 节点的
/etc/kubernetes/pki
目录。 - 为其他组件生成访问 kube-apiserver 所需的配置文件,这些文件的路径是
/etc/kubernetes/xxx.conf
。 - 为 Master 组件生成 Pod 配置文件,包括 kube-apiserver, kube-controller-manager 和 kube-scheduler,它们被生成在
/etc/kubernetes/manifests
。 - 生成一个 etcd 的 Pod YAML 文件,用来通过同样的 Static Pod 的方式启动 etcd。
- 为集群生成一个 bootsrap token,用于后续节点加入集群。
- 将
ca.crt
等 Master 节点的重要信息通过 ConfigMap 的方式保存在 etcd 当中。 - 安装默认插件,kube-proxy 和 DNS,用于提供整个集群的服务发现和 DNS 功能。
kube join 的工作流程
kubeadm 生成 bootstrap token 之后,就可以在任意一台安装了 kubelet 和 kubeadm 的机器上执行 kubeadm join
了。
配置 kubeadm 的部署参数
使用命令:
kubeadm init --config kubeadm.yaml
2 搭建完整 Kubernetes集群
1. 安装 kubeadm 和 Docker
添加 kubeadm 的源,然后直接安装即可,同时 kubelet、kubectl 和 kubernetes-cni 都会自动安装。
2. 部署 Kubernetes 的 Master 节点
部署 Master 节点只需要一行命令:
kubeadm init --config kubeadm.yaml
完成后,kubeadm 会生成一行指令:
kubeadm join 10.168.0.2:6443 --token 00wbx.uvnaa2ewjflwu1ry
--discovery-token-ca-cert-hash
sha256: 00eh62己2己6020f94132e3fe1abh721349bbcd3e9h94d己9654cfe15f2985ebd711
查看节点状态:
kubectl get nodes
查看详细信息:
kubectl describe node master
3. 部署网络插件
在一切皆容器的理念下,部署网络插件只需要一行命令:
kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"
部署完成后,可以通过 kubectl get
检查 Pod 的状态:
kubectl get pods -n kube-system
4. 部署 Kubernetes 的Worker节点
- 安装 kubeadm 和 Docker
- 执行部署 Master 时生成的
kubeadm join
指令
5. 通过 Taint/Toleration 调整 Master 执行 Pod 的策略
一旦某个节点被加上了一个 Taint,即”染上污点“,那么所有 Pod 都不能在该节点上运行,除非有个别 Pod 声明了 Toleration,它才可以在该节点上运行。
为节点加上污点的命令:
kubectl taint nodes node1 foo=bar:NoSchedule
NoSchedule
意味着不影响节点上已经在运行的 Pod,只会在调度新 Pod 时起作用。
声明 Toleration
只需要在 Pod 的 .yaml
文件的 spec
部分加入 tolerations
字段即可:
apiVersion: v1
kind: Pod
...
spec:
tolerations:
- key: "foo"
operator: "Equal"
value: "bar"
effect: "NoSchedule"
该 Pod 能“容忍”所有键值对为 foo=bar
的 Taint
。
6. 部署 Dashboard 可视化插件
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml
7. 部署容器存储插件
3. 第一个 Kubernetes 应用
Kubernetes 跟 Docker 等很多项目最大的不同是它不推荐你使用命令行的方式直接运行容器,而是希望你用 YAML 文件的方
式,即把容器的定义、参数、配置统统记录在一个 YAML 文件中,然后用这样一句指令把它运行起来:
kubectl create -f <配置文件>
一个配置文件的例子如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
namespace: hlwds
spec:
minReadySeconds: 5
selector:
matchLabels:
app: hlwds
template:
metadata:
labels:
app: hlwds
spec:
containers:
- env:
- name: TRAEFIK_HTTP_ROUTERS_QIMING_RULE
value: Host(`hlwds.hustonline.net`)
image: registry-vpc.cn-shenzhen.aliyuncs.com/bingyan_studio/qiming:7dfc04fe
livenessProbe:
initialDelaySeconds: 10
tcpSocket:
port: 80
name: app
ports:
- containerPort: 80
name: http
protocol: TCP
resources:
limits:
cpu: "2"
memory: 500Mi
requests:
cpu: 1m
memory: 10Mi
volumeMounts:
- mountPath: /app/db
name: hlwds-storage
volumes:
- name: hlwds-storage
persistentVolumeClaim:
claimName: hlwds-storage
其中 kind
指定了这个 API 对象的类型,是一个 Deployment
。
spec.template
中定义了一个 Pod 模板,描述了我想要的 Pod 的细节。
此外,每个 API 对象都有一个叫做 Metadata 的字段,这个字段就是 API 对象的“标识”,是从 Kubernetes 中找到这个对象的主要依据,最常用到的是 Labels
。
在 Metadata 中,还有一个与 Labels 格式、层级完全相同的字段,叫作 Annotations
,它专门用来携带键值对格式的内部信息。
kubectl get pods -l app=nginx
还可以用 kubectl describe
查看一个 API 对象的具体细节:
kubectl describe pod xxxxxx
其中一个重要的信息是 Events
,对 API 对象的所有重要操作都会被记录在里面。
如果要升级服务,只需要修改 .yaml
文件,然后执行命令:
kubectl apply -f xxx.yaml
还可以在 Deployment 中声明 Volume:
...
spec:
template:
...
volumes:
- name: nginx-vol
emptyDir: {}
最后,还可以使用 kubectl exec
进入 Pod 中:
kubectl exec -it nginx-deploymnt-xxxxx -- /bin/bash
若要在集群中删除 Deployment,则执行:
kubectl delede -f nginx-deployment.yaml
July 17th, 2023 at 06:18 pm
好耶