一些参考资料:
内容可能存在错误,欢迎纠正。
从 Docker 开始
Docker 等容器技术的出现和微服务大行其道脱不开关系。和一般的单体服务相比,微服务开销更小,单个服务的功能也更单一,这就对承载平台带来了挑战。不管是服务器还是虚拟机,只部署一个微服务显然浪费资源,部署多个微服务的话又难以隔离各类资源和运行环境。容器技术最大的特点就是把应用和运行环境打包、达到了开箱即用的效果,这也就解决了上面的问题,我们能在不浪费服务器资源的同时隔离各个服务,也为更好的应用监控、资源分配等打下了基础。从此应用要面对的不再是 OS 乃至机器,而是容器。
有了标准化的交付手段,换台机器就不能跑的时代成为历史啦!
Docker 是使用最广泛的开源容器引擎,基于操作系统级的虚拟化技术 runc,依赖 Linux 内核特性 Namespace(环境隔离) 和 Cgroup(资源限制)。不多解释,毕竟我也不是很懂。
Docker 容器与容器管理
我们可以把 Docker 容器大致分为 4 类:
- 无状态服务(例如微服务)
- 有状态服务(例如数据库)
- Job(例如数据分析任务)
- 工具(例如 netshoot)
这样分类的目的是总结出创建容器(docker run
)时常用的选项参数。其中无状态服务和有状态服务都需要长时间运行,所以它们经常要用到 -d
让容器在后台运行,用 -P
将容器所暴露的所有端口随即映射到宿主机的端口(-p
会将容器暴露的端口映射到宿主对应的端口,但很可能遇到宿主端口被占用的请况),有状态服务还可能要操作文件,比方说指定保存数据的目录,这时就要用到 -v
把宿主机的目录挂载到容器里;Job 类的容器通常运行完之后就可以直接退出,不用挂到后台,不过遇到要处理数据的情况就要用到 -v
了;工具类的容器可能需要交互,所以经常会用到 -it
,交互式运行并分配一个伪终端。
除此之外,管理容器常用的命令还有 docker
…
ps
:列出容器inspect
:查看容器的详细信息exec
:在容器中执行命令logs
:获取容器输出到stdout
和stderr
的日志rm
:删除容器
当然,我们可以利用 help 或者文档来查看更多的命令选项和管理方法。
Docker 镜像
Docker 镜像本身是一个分层存储的文件,按照创建时的命令顺序依次“向上堆叠”。另外,容器启动的时候会创建一个可写的容器层,所有的文件操作都发生在这里,容器不会修改镜像层的文件。
有关镜像的命令通常是 docker image ...
。
Dockerfile
Dockerfile 中常用的指令:
指令 | 描述 |
---|---|
FROM | 说明基于哪个镜像来构建这个镜像 |
MAINTAINER | 镜像维护者 |
RUN | 构建镜像时运行的 shell 命令 |
COPY | 将文件/目录复制到镜像中 |
ADD | 将文件/目录添加到镜像中,支持通过 URL 远程获取资源 |
ENV | 设置环境变量 |
USER | 为 RUN 、CMD 和 ENTRYPOINT 指定用户 |
EXPOSE | 声明容器运行的服务端口 |
WORKDIR | 为 RUN 、CMD 、ENTRYPOINT 、COPY 和 ADD 设置工作目录 |
ENTRYPOINT | 运行容器时执行,如果有多个,最后一个生效 |
CMD | 运行容器时执行,如果有多个,最后一个生效,能够被 docker run 的参数替换 |
为什么不能用 RUN cd xxx
来更改工作目录?每一个 RUN
都相当于启动一个容器、执行命令然后提交文件变更,后面的命令并不继承前一层的变化。
说到 K8s
我们一般把 K8s 称为容器管理平台,在“云原生”的世界中,它扮演了类似于操作系统的角色,对上提供一组功能和接口给云原生应用,对下则管理网络、存储等资源。
K8s 在很长一段时间里用 Docker 作为容器运行时环境。虽然现在 K8s 已经抛弃 Docker 转向了 containerd,但是 Docker 镜像已经成为事实上的标准,我们还是可以在本地用 Docker 开发,部署时再使用 containerd。
K8s 的架构
- kuberctl:管理 K8s 环境的命令行工具
- ApiServer:操作 K8s 资源的入口
- Scheduler:负责将 Pod 调度到合适的 Node 上
- Controller-manager:由负责不同资源的多个 Controller 构成,共同负责集群内的 Node、Pod 等所有资源的管理
- etcd:K8s 集群的数据库
- Node
- kubelet:部署在每个 Node 节点上,向 ApiServer 注册,管理节点上的 pod,检查容器健康状态,通过 Metrics Server 等监控 Node、Pod 的资源使用情况
- kube-proxy:网络代理与负载均衡
- Pod:K8s 的最小调度单元(相当于虚拟机?),一个 Pod 可以封装多个容器,容器之间共享存储、网络等
为什么不直接管理容器?
Pod 中的容器
Pod 的声明文件说明了 Pod 创建完成的状态。
# cndevtutorial/pods/pod-2containers.yaml
apiVersion: v1
kind: Pod
metadata:
name: 2containers
labels:
app: 2containers
spec:
containers:
- name: content-generator
image: centos:7
command: ["bash","-c",
"while true;
do echo $(date) > /contentdir/index.html;
sleep 1;done"]
volumeMounts:
- name: web-content
mountPath: /contentdir/
- name: web-server
image: nginx:1.19-alpine-perl
ports:
- containerPort: 80
volumeMounts:
- name: web-content
mountPath: /usr/share/nginx/html/
volumes:
- name: web-content
emptyDir: {}
如上所示,可以用列表的形式声明 Pod 中的多个容器。
上面的 Pod 用到了一个启动时为空的临时卷,这个卷的生命周期和 Pod 一样。
command
会覆盖容器的 ENTRYPOINT
?
一些其他的点:
- Sidecar:例如用一个容器运行主服务,另一个容器收集非标准输出的日志
- initContainers:和 containers 同级,为其他容器准备数据和环境等,按声明的顺序运行,所有 initContainer 运行成功之后才会启动其他容器
工作负载类型/Pod 的工作策略
K8s 提供了几种工作负载类型,方便用户管理一组 Pod,而不是直接管理每个 Pod。
ReplicaSet/Deployment
ReplicaSet 可以提供一组一定数量的 Pod 副本,而 Deployment 管理 ReplicaSet,还拥有滚动升级等特性,所以一般使用 Deployment,不直接用 ReplicaSet。Deployment 适用于无状态服务。
Deployment 的例子:
# cndevtutorial/deploy-service/deployment-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-demo
spec:
selector:
matchLabels:
app: spring-demo
tier: front-end
replicas: 3
template:
metadata:
labels:
app: spring-demo
tier: front-end
spec:
containers:
- name: springmsdemo
image: limingyu007/springmsdemo:1.0
Deployment 用 matchLabels
来匹配 Pod 声明,即 template
,replicas
指定了 Pod 数量。在滚动更新时,Deployment 创建新的 ReplicaSet,启动一个新的 Pod,成功之后关闭一个老的 Pod,如此直到全部替换完成。
扩容缩容就是通过更改 replicas
实现的;每次更新 Pod 的数量等可以通过 Deployment 的 spec.strategy
来配置。
StatefulSet
StatefulSet 负责管理有状态应用。
StatefulSet 的例子:
# https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/statefulset/#components
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None # 'Headless' - by me
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx # 必须匹配 .spec.template.metadata.labels
serviceName: "nginx"
replicas: 3 # 默认值是 1
minReadySeconds: 10 # 默认值是 0
template:
metadata:
labels:
app: nginx # 必须匹配 .spec.selector.matchLabels
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: registry.k8s.io/nginx-slim:0.24
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "my-storage-class"
resources:
requests:
storage: 1Gi
ReadWriteOnce
只允许一个 Pod 访问卷,但是允许多个容器同时操作。使用 ReadWriteOncePod
可以保证一次只有一个容器访问卷。
重新创建 StatefulSet 时,Pod 名/HostName 不会改变。
DaemonSet
DaemonSet 确保全部(或某些)节点上运行一个 Pod 的副本。当有节点加入集群时,也会为它们新增一个 Pod。当有节点从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod。常见用法有节点上的监控、日志收集等。
Job 和 CronJob
Job 和 CronJob 可以定义运行结束后可以退出的任务。
其他
Pod 的资源约束,如计算资源